Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Edistynyt mobiiliohjelmointi, 28.3.2023
- // Tehdään uusi fragment: CustomViewTesterFragment (mobile_navigation-työkalun kautta) -> laitetaan päävalikkoon -> binding layer käyttöön
- Esim, CustomViewTesterFragment:
- class CustomViewTesterFragment : Fragment() {
- // change this to match your fragment name
- private var _binding: FragmentCustomViewTesterBinding? = 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 = FragmentCustomViewTesterBinding.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
- }
- }
- // vaihdetaan ulkoasuksi tähän fragmentiin LinearLayout helppoa testaamista varten
- <?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=".CustomViewTesterFragment">
- </LinearLayout>
- // Tehdään uusi kotlin-luokka: CustomTemperatureView:
- 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
- }
- }
- // laitetaan CustomViewTesterFragmentin ulkoasuun XML:n oma custom -komponentti:
- // HUOM: paketin nimi on omassa projektissasi eri
- <com.example.edistynytmobiili2023_21a.CustomTemperatureView
- android:id="@+id/customtemperatureview_test"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- // kokeillaan hieman lisää säätää ympyrää ja tekstiä, ei vieläkään kovin hyvä
- init
- {
- // define the colors!
- paint.color = Color.BLUE
- textPaint.color = Color.BLACK
- textPaint.textSize = 70f
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(100f, 100f, 100f, paint)
- // parameters: content, x, y, color
- canvas.drawText("Test!", 50f, 100f, textPaint);
- }
- // muutetaan onMeasure, jotta voidaan käyttää xml:ssä tarkkoja dp-kokoja
- 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.BLACK
- textPaint.textSize = 70f
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(100f, 100f, 100f, paint)
- // parameters: content, x, y, color
- canvas.drawText("Test!", 50f, 100f, textPaint);
- }
- // jos ulkoasussa ei anneta tarkkaa kokoa, tämä on default-koko
- var size = 200
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // 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)
- }
- }
- // nyt XML:ssä toimii näin:
- <com.example.edistynytmobiili2023_21a.CustomTemperatureView
- android:id="@+id/customtemperatureview_test"
- android:layout_width="200dp"
- android:layout_height="200dp" />
- // vielä hienosäätöä:
- 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 = 85f
- 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)
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
- // parameters: content, x, y, color
- canvas.drawText("Test!", width.toFloat() / 2, width.toFloat() / 2 + 24, textPaint);
- }
- // jos ulkoasussa ei anneta tarkkaa kokoa, tämä on default-koko
- var size = 200
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // 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)
- }
- }
- // annetaan mahdollisuus myös vaihtaa lämpötilaa fragmentista:
- 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 = 85f
- textPaint.textAlign = Paint.Align.CENTER
- textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
- }
- // muuttuja, joka pitää kirjaa aktiivisesta lämpötilasta
- private var temperature = 0
- // funktio, joka pystyy muuttamaan aktiviista lämpötilaa esim. fragmentista käsin
- fun changeTemperature(temp : Int) {
- temperature = temp
- // muutetaan ympyrän taustaväri lämpötilan perusteella
- if(temperature > 0) {
- paint.color = Color.RED
- }
- else {
- paint.color = Color.BLUE
- }
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
- // parameters: content, x, y, color
- canvas.drawText("${temperature}℃", width.toFloat() / 2, width.toFloat() / 2 + 25, textPaint);
- }
- // jos ulkoasussa ei anneta tarkkaa kokoa, tämä on default-koko
- var size = 200
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // 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)
- }
- }
- // lisätään myös komponentin päivityslogiikka changeTemperatureen:
- class CustomTemperatureView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : View(context, attrs, defStyleAttr) {
- // aluastetaan väriobjektit
- 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 = 85f
- textPaint.textAlign = Paint.Align.CENTER
- textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
- }
- // muuttuja, joka pitää kirjaa aktiivisesta lämpötilasta
- private var temperature = 0
- // funktio, joka pystyy muuttamaan aktiviista lämpötilaa esim. fragmentista käsin
- fun changeTemperature(temp : Int) {
- temperature = temp
- // muutetaan ympyrän taustaväri lämpötilan perusteella
- if(temperature > 0) {
- paint.color = Color.RED
- }
- else {
- paint.color = Color.BLUE
- }
- // arvot muuttui, kerrotaan Androidille että piirretään tämä komponentti uudestaan
- invalidate()
- requestLayout()
- }
- // drawing is typically done in the custom view's onDraw-method
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- // parameters: x-coordinate, y-coordinate, size, color
- canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
- // parameters: content, x, y, color
- canvas.drawText("${temperature}℃", width.toFloat() / 2, width.toFloat() / 2 + 25, textPaint);
- }
- // jos ulkoasussa ei anneta tarkkaa kokoa, tämä on default-koko
- var size = 200
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // 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)
- }
- }
- // lisätään XML:ään myös nappi testaamista varten:
- <com.example.edistynytmobiili2023_21a.CustomTemperatureView
- android:id="@+id/customtemperatureview_test"
- android:layout_width="120dp"
- android:layout_height="120dp" />
- <Button
- android:id="@+id/button_change_temperature"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="CHANGE TEMPERATURE" />
- // fragmentin onCreateView -> kokeillaan vaihtaa arvoja:
- // the binding -object allows you to access views in the layout, textviews etc.
- binding.customtemperatureviewTest.changeTemperature(7)
- binding.buttonChangeTemperature.setOnClickListener {
- binding.customtemperatureviewTest.changeTemperature(-5)
- }
- // nyt komponenttia voi käyttää myös WeatherStation MQTT-datalla, esim. ulkoasuun
- <com.example.edistynytmobiili2023_21a.CustomTemperatureView
- android:id="@+id/customtemperatureview_weatherstation"
- android:layout_width="120dp"
- android:layout_height="120dp"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
- // ja MQTT-koodissa (subscribe):
- activity?.runOnUiThread(java.lang.Runnable {
- binding.customtemperatureviewWeatherstation.changeTemperature(temperatureValue.toInt())
- })
- #########################################################
- compound controlit, LatestDataView
- #########################################################
- Uusi Kotlin-luokka: LatestDataView
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // konstruktori ja alkuarvot
- init {
- }
- // funktio jonka kautta esim. fragment voi lisätä viestin listaan
- fun addData(message : String) {
- }
- }
- // lisätään ensimmäiset ominaisuudet, addDatan kautta voidaan lisätä textViewejä listaan:
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // konstruktori ja alkuarvot
- init {
- this.orientation = VERTICAL
- addData("Testiviesti 1")
- addData("Testiviesti 2")
- }
- // funktio jonka kautta esim. fragment voi lisätä viestin listaan
- 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)
- }
- }
- // asetetaan custom view test fragmenttiin, lisätään myös nappi alle että voidaan lisätä testiviestejä:
- <com.example.edistynytmobiili2023_21a.LatestDataView
- android:id="@+id/latestdataview_test"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- <Button
- android:id="@+id/button_add_test_message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Add message" />
- // nyt custom view test fragmentissa, kytketään nappi että voidaan testata:
- // napilla testataan että saadaan uusi viesti LatestDataViewiin
- binding.buttonAddTestMessage.setOnClickListener {
- var text = UUID.randomUUID().toString()
- binding.latestdataviewTest.addData(text)
- }
- // tehdään res -> drawable -kansioon uusi xml-tiedosto -> customborder
- <?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="#28de58"/>
- </shape>
- // jotta taustareunus näkyy, lisätään LatestDataViewiin paddingia ja otetaan background käyttöön kyseiselle customborderille
- <com.example.edistynytmobiili2023_21a.LatestDataView
- android:id="@+id/latestdataview_test"
- android:background="@drawable/customborder"
- android:padding="5dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- // hienosäädetään jotta aloituskorkeus on myös valmiiksi viiden textviewin korkuinen
- // maksimimäärä rivejä joita lista näyttää
- val maxRows = 5
- // konstruktori ja alkuarvot
- init {
- this.orientation = VERTICAL
- // jotta aloituskorkeus saadaan heti alussa viiden TextViewin kokoiseksi (huolimatta siitä
- // kuinka monta TextViewiä sisällä on, voidaan tehdä jotain seuraavaa:
- // tehdään koodin muistiin TextView (jota ei ole tarkoitus käyttää missään) ja otetaan siitä
- // yhden TextViewin korkeus näytöllä
- var someTextView : TextView = TextView(context)
- someTextView.measure(0,0)
- var rowHeight = someTextView.measuredHeight
- // otetaan selvää kuinka korkea LinearLayout on alussa näytöllä
- this.measure(0, 0)
- // asetetaan LatestDataViewin aloituskorkeudeksi:
- // LinearLayoutin alkukorkeus + 5 * TextViewin korkeus
- var additionalHeight = this.measuredHeight + maxRows * rowHeight
- this.minimumHeight = additionalHeight
- }
- // funktio jonka kautta esim. fragment voi lisätä viestin listaan
- fun addData(message : String) {
- // niin kauan kuin listassa on enemmän kuin 5 textViewiä, poistetaan
- // vanhin textview
- while(this.childCount >= maxRows) {
- this.removeViewAt(0)
- }
- var newTextView : TextView = TextView(context) as TextView
- newTextView.setText(message)
- newTextView.setBackgroundColor(Color.BLACK)
- newTextView.setTextColor(Color.YELLOW)
- this.addView(newTextView)
- }
- // fade in animaatio, res-kansioon uusi kansio -> anim , ja sinne uusi xml -> customfade
- <?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>
- // animaation käyttäminen esim. LatestDataViewissä, kun uutta TextViewiä laitetaan näytölle:
- // animoidaan uusi TextView fadein-animaatiolla, res -> anim -> customfade
- val fadeAnimation: Animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
- newTextView.startAnimation(fadeAnimation)
- // jos nyt lisätään WeatherStationFragmentiin uusi LatestDataView tällä id:llä: latestdataview_weather_station
- // voidaan koodissa kytkeä sääaseman datat siihen kiinni, esim:
- var timeStamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
- var text = "${timeStamp} - Temperature: ${temperatureValue}℃, Humidity: ${item.d.get3().v.toFloat()}%"
- activity?.runOnUiThread(java.lang.Runnable {
- binding.latestdataviewWeatherStation.addData(text)
- })
- // robottianimaatio, kopioi ensin frametiedostot drawableihin, eli esim idle1.png, idle2png jne
- // huom: robotanimation.xml tulee drawable-kansion sisälle, ei anim-kansion sisälle
- <?xml version="1.0" encoding="utf-8"?>
- <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <item
- android:duration="70"
- android:drawable="@drawable/idle1" />
- <item
- android:duration="70"
- android:drawable="@drawable/idle2" />
- <item
- android:duration="70"
- android:drawable="@drawable/idle3" />
- <!-- ym. loput framet miten haluat -->
- </animation-list>
- // fragmentin ulkoasuun:
- <ImageView
- android:id="@+id/imageView_robot"
- android:layout_width="170dp"
- android:layout_height="300dp"
- android:background="@drawable/idle1" />
- // fragmentin koodissa, animaation käynnistäminen:
- binding.imageViewRobot.setBackgroundResource(R.drawable.robotanimation)
- val frameAnimation = binding.imageViewRobot.background as AnimationDrawable
- frameAnimation.start()
Advertisement
Add Comment
Please, Sign In to add comment