Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Edistynyt mobiiliohjelmointi, 27.3.2023
- // tehdään aluksi oma Kotlin-luokka custom viewiä varten:
- class CustomTemperatureView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : View(context, attrs, defStyleAttr) {
- init
- {
- // this is constructor of your component
- // all initializations go here
- }
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // here you can do all the drawing
- }
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // Android uses this to determine the exact size of your component on screen
- }
- }
- // tehdään erillinen fragment Custom View -komponenttien testaamiselle, esim. CustomViewTestFragment:
- class CustomViewTestFragment : Fragment() {
- // change this to match your fragment name
- private var _binding: FragmentCustomViewTestBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentCustomViewTestBinding.inflate(inflater, container, false)
- val root: View = binding.root
- // the binding -object allows you to access views in the layout, textviews etc.
- return root
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
- // CustomViewTesterFragmentin ulkoasuun LinearLayout testaamisen helpottamiseksi:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_margin="5dp"
- tools:context=".CustomViewTestFragment">
- </LinearLayout>
- // oman custom viewin lisääminen on kuin lisäisi minkä tahansa muun viewin ulkoasuun:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_margin="5dp"
- tools:context=".CustomViewTestFragment">
- <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </LinearLayout>
- // piirretään lisää tavaraa
- class CustomTemperatureView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : View(context, attrs, defStyleAttr) {
- private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
- private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
- init
- {
- // define the colors!
- paint.color = Color.BLUE
- textPaint.color = Color.WHITE
- textPaint.textSize = 75f
- textPaint.textAlign = Paint.Align.CENTER
- textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // you can do all the drawing through the canvas-object
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
- // parameters: content, x, y, color. little offset to y-axis to bump it to center
- canvas.drawText("Test!", width.toFloat() / 2, width.toFloat() / 2 + 30, textPaint);
- }
- var size : Int = 200
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- // Try for a width based on our minimum
- val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
- var w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1)
- // if no exact size given (either dp or match_parent)
- // use this one instead as default (wrap_content)
- if (w == 0)
- {
- w = size * 2
- }
- // Whatever the width ends up being, ask for a height that would let the view
- // get as big as it can
- // val minh: Int = View.MeasureSpec.getSize(w) + paddingBottom + paddingTop
- // in this case, we use the height the same as our width, since it's a circle
- val h: Int = View.resolveSizeAndState(
- View.MeasureSpec.getSize(w),
- heightMeasureSpec,
- 0
- )
- setMeasuredDimension(w, h)
- }
- }
- // asetetaan nyt kooksi 120x120dp
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_margin="5dp"
- tools:context=".CustomViewTestFragment">
- <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
- android:layout_width="120dp"
- android:layout_height="120dp" />
- </LinearLayout>
- // viimeinen versio customviewistä:
- class CustomTemperatureView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : View(context, attrs, defStyleAttr) {
- private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
- private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
- // konstruktori
- init
- {
- // define the colors and sizes!
- paint.color = Color.BLUE
- textPaint.color = Color.WHITE
- textPaint.textSize = 80f
- textPaint.textAlign = Paint.Align.CENTER
- textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
- }
- // tämä muuttuja pitää kirjaa aktiivisesta lämpötilasta
- private var temperature : Int = 0
- // muutetaan lämpötilaa fragmentin kautta
- fun changeTemperature(temp : Int) {
- temperature = temp
- // tee tämä tehtävänannon mukaisesti viidelle eri värille
- if(temperature > 0) {
- paint.color = Color.RED
- }
- else {
- paint.color = Color.BLUE
- }
- // tuli muutos dataan, ilmoitetaan Androidille
- // että pitää piirtä komponentti uudestaan
- invalidate()
- requestLayout()
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // you can do all the drawing through the canvas-object
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
- // parameters: content, x, y, color. little offset to y-axis to bump it to center
- canvas.drawText("${temperature}℃", width.toFloat() / 2, width.toFloat() / 2 + 30, textPaint);
- }
- // default view size if nothing is given in layout (wrap_content)
- var size : Int = 200
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- // Try for a width based on our minimum
- val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
- var w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1)
- // if no exact size given (either dp or match_parent)
- // use this one instead as default (wrap_content)
- if (w == 0)
- {
- w = size * 2
- }
- // Whatever the width ends up being, ask for a height that would let the view
- // get as big as it can
- // val minh: Int = View.MeasureSpec.getSize(w) + paddingBottom + paddingTop
- // in this case, we use the height the same as our width, since it's a circle
- val h: Int = View.resolveSizeAndState(
- View.MeasureSpec.getSize(w),
- heightMeasureSpec,
- 0
- )
- setMeasuredDimension(w, h)
- }
- }
- // jos haluat testata CustomViewTestFragmentissa että changeTemperature-funktio toimii:
- // testataan että lämpötilaa voidaan vaihtaa
- // jos lämpötila ei vaihdu, tarkista että
- // changeTemperaturen viimeisinä rivinä on invalidate() ja requestLayout()
- binding.customtemperatureviewTest.changeTemperature(5)
- binding.buttonChangeTemperature.setOnClickListener {
- binding.customtemperatureviewTest.changeTemperature(-10)
- }
- // voidaan kokeilla myös kytketä MQTT:ssä kiinni olevaan sääasemaan, esim ulkoasu:
- <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
- android:id="@+id/customtemperatureview_weatherstation"
- android:layout_width="120dp"
- android:layout_height="120dp"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/speedView" />
- // ajetaan binding-layeria koskevat asia UI-threadissa erikseen
- activity?.runOnUiThread(java.lang.Runnable {
- binding.customtemperatureviewWeatherstation.changeTemperature(temperature.toInt())
- })
- ######################################################
- Custom viewit, osa 2 : Compound control
- ######################################################
- Tehdään uusi Kotlin-luokka, LatestDataView.kt:
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // konstruktori, säädetään orientaatio
- init {
- this.orientation = VERTICAL
- }
- }
- // lisätään apufunktio jolla voidaan lisätä lennosta uusi TextView
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // konstruktori, säädetään orientaatio
- init {
- this.orientation = VERTICAL
- addData("Testiviesti 1")
- addData("Testiviesti 2")
- }
- // this function can be called where it's needed, init() or an Activity.
- fun addData(message : String)
- {
- var newTextView : TextView = TextView(context) as TextView
- newTextView.setText(message)
- newTextView.setBackgroundColor(Color.BLACK)
- newTextView.setTextColor(Color.YELLOW)
- this.addView(newTextView)
- }
- }
- // lisätään LatestDataView fragmenttiin (custom view test fragment)
- <com.example.edistynytmobiiliohjelmointi2023b.LatestDataView
- android:id="@+id/latestdataview_test"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- <Button
- android:id="@+id/button_add_message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="ADD DATA" />
- // jos halutaan kokeilla lisätä viestejä Fragmentissa:
- // lisätään satunnainen testiviesti
- binding.buttonAddMessage.setOnClickListener {
- var text = UUID.randomUUID().toString()
- binding.latestdataviewTest.addData(text)
- }
- // tehdään customborder.xml drawable-kansioon:
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <stroke
- android:width="5dp"
- android:color="#42f560" />
- </shape>
- // otetaan käyttöön ulkoasussa, tarvitaan paddingia jotta taustakuva tulee esille
- // huomaa: borderin leveys ja padding täsmää
- <com.example.edistynytmobiiliohjelmointi2023b.LatestDataView
- android:id="@+id/latestdataview_test"
- android:background="@drawable/customborder"
- android:padding="5dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- // vain 5 riviä sallitaan TextViewejä kerralla:
- // maksimimäärä rivejä LatestDataViewissä on 5 riviä
- var maxRows : Int = 5
- // this function can be called where it's needed, init() or an Activity.
- fun addData(message : String)
- {
- // poistetaan vanhin viesti että näkyy vain max 5 riviä
- while(this.childCount >= maxRows) {
- this.removeViewAt(0)
- }
- // luodaan uusi textview lennosta ja laitetaan linearlayoutiin
- var newTextView : TextView = TextView(context) as TextView
- newTextView.setText(message)
- newTextView.setBackgroundColor(Color.BLACK)
- newTextView.setTextColor(Color.YELLOW)
- this.addView(newTextView)
- // animoidaan fade in -animaatio
- val animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
- newTextView.startAnimation(animation)
- }
- // pientä hienosäätöä, jotta LatestDataView on heti alussa oikean korkuinen:
- init {
- this.orientation = VERTICAL
- // idea: luodaan ohjelman muistiin yksi textview, ja otetaan sen koko talteen
- // asetetaan LatestDataViewin alkukorkeudeuksi peruskorkeus + viiden TextViewin yhteenlaskettu korkeus
- var someTextView : TextView = TextView(context)
- // pyydetään Androidia mittaamaan tämän komponentin koko,
- // jos se nyt laitettaisiin tämän puhelimen näytölle esille
- someTextView.measure(0,0)
- // lasketaan viiden textviewin tarvitsema pystytila
- var additionalHeight = someTextView.measuredHeight * maxRows
- // mitataan myös itse LinearLayoutin alkukoko
- this.measure(0, 0)
- var startHeight = this.measuredHeight
- // asetetaan laskennallinen korkeus, johon mahtuu tasan viisi textviewiä
- this.minimumHeight = startHeight + additionalHeight
- }
- // tehdään res-kansioon uusi kansio -> anim
- // lisätään customfade.xml anim-kansioon:
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:anim/linear_interpolator">
- <alpha
- android:duration="2000"
- android:fromAlpha="0.1"
- android:toAlpha="1.0">
- </alpha>
- </set>
- // lisätään addData()-funktion loppuun animaation käyttöönotto uudelle TextViewille:
- // animoidaan fade in -animaatio
- val animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
- newTextView.startAnimation(animation)
- // kokeillaan kytkeä MQTT-sääasemaan:
- // haetaan aikaleima ja rakennetaan viesti LatestDataViewiin
- val timeStamp: String = SimpleDateFormat("HH:mm:ss").format(Date())
- var message = "${timeStamp} - Temperature: ${temperature}℃, humidity: ${item.d.get3().v.toInt()}%"
- // ajetaan binding-layeria koskevat asia UI-threadissa erikseen
- activity?.runOnUiThread(java.lang.Runnable {
- binding.latestdataviewWeatherstation.addData(message)
- })
- // frame by frame animaatiot:
- https://bignerdranch.com/blog/frame-animations-in-android/
- // tehdään esim. robotanimation.xml drawable -kansioon, lisätään framet kuvatiedostoina projektiin (myös drawable-kansioon)
- // animaation käynnistäminen, esim. CustomViewTestFragment:
- binding.imageviewAnimated.setBackgroundResource(R.drawable.robotanimation)
- (binding.imageviewAnimated.getBackground() as AnimationDrawable).start()
- // XML:ssä pitää olla ImageView:
- <ImageView
- android:id="@+id/imageview_animated"
- android:layout_width="200dp"
- android:layout_height="wrap_content"
- android:background="@drawable/idle1" />
Advertisement
Add Comment
Please, Sign In to add comment