Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Edistynyt mobiiliohjelmointi, 13.4.2023
- !!! HOX! CUSTOM VIEWEIHIN LIITTYVÄT MUISTIINPANOT ALEMPANA!!!
- ###############################
- ANDROID-SOVELLUKSEN VIIMEISTELY
- ###############################
- // lokalisaatiot
- Android Studiossa vasen ylänurkka -> Android-näkymästä -> Project-näkymäksi
- Tehdään app -> src -> main -> res -kansioon kaksi uutta kansiota:
- values-en
- values-fi
- Kopioidaan values-kansion strings.xml -tiedostot molempiin kansioihin pohjaksi. Tämän jälkeen clean project ja rebuild project. Tämän jälkeen voit avata minkä tahansa strings.xml -tiedoston, ja klikata oikeasta ylänurkasta Open Editor.
- Lisätään greeting_text avaimella uusi käännös:
- Default: Welcome!
- English: Welcome!
- Finnish: Tervetuloa!
- Kokeillaan homefragmentin ulkoasussa näyttää tämä viesti:
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="20dp"
- android:text="@string/greeting_text"
- android:textSize="32sp"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- // tämän jälkeen tervetuloteksti on joko suomeksi tai englanniksi riippuen puhelimen kielestä.
- // tehdään myös 10" -tableteille oma layout-kansio => src -> res -> layout-sw720dp.
- Kopioidaan fragment_home.xml tähän kansioon pohjaksi.
- Muutetaan Welcome-teksti isommaksi, eri väriseksi ja kursivoiduksi.
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="20dp"
- android:text="@string/greeting_text"
- android:textColor="#9B54D8"
- android:textSize="140sp"
- android:textStyle="bold|italic"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- Nyt jos kokeilet 10" tabletin emulaattorilla etusivua, näkyy Welcome-teksti erilaisena, koska olemme tehneet isoille tableteille oman täsmäulkoasun.
- ##############################################
- CUSTOM VIEWIEN TEKEMINEN ALUSTA ASTI
- ##############################################
- Omien custom viewien testaamista varten on hyvä tehdä oma testifragment. esim. CustomViewTesterFragment
- mobile_navigation.xml -> uusi destination -> laitetaan päävalikkoon (menu -> main_activity_drawer, lisätään listaan MainActivity.kt)
- Otetaan myös binding layer käyttöön tässä fragmentissa.
- 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
- }
- }
- ulkoasuun myös linearlayout käyttöön jotta testaaminen on helpompaa:
- <?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"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:layout_margin="10dp"
- 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) {
- // your helper variables etc. can be here
- 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
- }
- }
- // lisätään valmiiksi komponentti jo ulkoasuun myös:
- <com.example.android2023tv.CustomTemperatureView
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- HUOM: paketin nimi on omassa projektissasi eri, voi alkaa kirjoittamaan vain CustomTemperatureView xml:ään, ja Android Studio ehdottaa oikeaa nimeä.
- kokeillaan piirtää jotakin
- 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)
- // your helper variables etc. can be here
- init {
- paint.color = Color.BLUE
- textPaint.color = Color.BLACK
- }
- 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(100f, 100f, 100f, paint)
- // parameters: content, x, y, color
- canvas.drawText("Test!", 10f, 10f, textPaint);
- }
- override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- // Android uses this to determine the exact size of your component on screen
- }
- }
- drawCircle aloittaa piirtämisen ympyrän keskeltä, sen tkaia x ja y on 100f jotta koko ympyrä piirtyy näytölle (kohdassa 0 ja 0 piirtää suuren osan ympyrästä näytön ulkopuolelle.)
- teksti on myös aivan liian pieni.
- lisätään onMeasure, jotta CustomView lukee kokotiedot XML:stä, muutetaan XML:
- <com.example.android2023tv.CustomTemperatureView
- android:layout_width="120dp"
- android:layout_height="wrap_content" />
- // CustomView:
- 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)
- // your helper variables etc. can be here
- init {
- paint.color = Color.BLUE
- textPaint.color = Color.BLACK
- textPaint.textSize = 60f
- // keskitetään sivusuunnassa
- textPaint.textAlign = Paint.Align.CENTER
- }
- // koska nyt on käytössä dynaaminen onMeasure, voidaan
- // käyttää width.toFloat() hyödyksi koordinaattien laskemisessa
- 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
- canvas.drawText("Test!", width.toFloat() / 2, width.toFloat() / 2, textPaint);
- }
- // oletuskoko viewille, jos käytetään "wrap_content"
- val size = 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)
- }
- }
- // säädetään vielä lisää:
- // your helper variables etc. can be here
- init {
- paint.color = Color.BLUE
- textPaint.color = Color.WHITE
- // muutetaan tekstin koko, lihavointi ja asetetaan sivusuunnassa keskelle
- textPaint.textSize = 90f
- textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
- textPaint.textAlign = Paint.Align.CENTER
- }
- // koska nyt on käytössä dynaaminen onMeasure, voidaan
- // käyttää width.toFloat() hyödyksi koordinaattien laskemisessa
- 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
- // pieni offset y-akseliin, että teksti menee keskelle
- // parempi ois koodilla ottaa selvä kuinka korkeaa teksti on ja jakaa se kahdella ja lisätä offsetiksi
- canvas.drawText("Test!", width.toFloat() / 2, width.toFloat() / 2 + 26, textPaint);
- }
- // kokeillaan testilämpötilalla:
- canvas.drawText("-32℃", width.toFloat() / 2, width.toFloat() / 2 + 26, textPaint);
- // jotta voidaan keskittää lämpötila muuttujaan, tehdään CustomViewiin luokkatasolle apumuuttuja:
- private var temperature : Int = 0
- ja kytketään apumuuttuja onDrawiin:
- canvas.drawText("${temperature}℃", width.toFloat() / 2, width.toFloat() / 2 + 28, textPaint);
- käytännössä nyt jos muokkaamme temperature-muuttujaa ulkopuolelta, se näkyy myös näytöllä.
- Tehdään julkinen apufunktio, minkä kautta voidaan lämpötilaa vaihtaa myös fragmentista käsin:
- // pidetään kirjaa siitä mikä on aktiivinen lämpötila
- private var temperature : Int = 0
- // funktio, mikä asettaa uuden aktiivisen lämpötilan
- // esim. fragmentin kautta kutsuttuna
- fun changeTemperature(t : Int) {
- temperature = t
- // tiedot muuttui, ilmoitetaan Androidille,
- // että pitää piirtää View uudestaan
- invalidate()
- requestLayout()
- }
- // muutetaan myös väriä lämpötilan vaihtuessa
- // funktio, mikä asettaa uuden aktiivisen lämpötilan
- // esim. fragmentin kautta kutsuttuna
- fun changeTemperature(t : Int) {
- temperature = t
- // muutetaan taustaväriä lämpötilan perusteella
- if(temperature > 0) {
- paint.color = Color.RED
- }
- else {
- paint.color = Color.BLUE
- }
- // tiedot muuttui, ilmoitetaan Androidille,
- // että pitää piirtää View uudestaan
- invalidate()
- requestLayout()
- }
- // lisätään ulkoasuun id customviewille, ja lisätään myös button jossa id
- <com.example.android2023tv.CustomTemperatureView
- android:id="@+id/customtemperatureview_tester"
- android:layout_width="120dp"
- android:layout_height="wrap_content" />
- <Button
- android:id="@+id/button_change_temperature"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Button" />
- // kokeillaan asettaa satunnainen lämpötila:
- // arvotaan Buttonin pohjalta satunnainen lämpötila
- binding.buttonChangeTemperature.setOnClickListener {
- val temp = Random.nextInt(-50, 50)
- binding.customtemperatureviewTester.changeTemperature(temp)
- }
- // CustomView on jo aika käyttökelpoinen, kokeillaan esim WeatherStationFragmentissa:
- ulkoasuun:
- <com.example.android2023tv.CustomTemperatureView
- android:id="@+id/customtemperatureview_weatherstation"
- android:layout_width="120dp"
- android:layout_height="wrap_content" />
- Kotlinissa (huom: temperature on sääaseman dataa, lämpötila Double-muodossa):
- activity?.runOnUiThread {
- binding.customtemperatureviewWeatherstation.changeTemperature(temperature.toInt())
- }
- ##################################
- COMPOUND CONTROL - LatestDataView
- ##################################
- Tehdään uusi Kotlin-luokka projektiin, nimeltään LatestDataView, ja otetaan sopiva pohja Moodlesta:
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // rest of the basic methods here from the template above
- }
- huom: ei tarvitse tehdä itse onDraw, onMeasure ym. koska ne ovat jo LinearLayoutissa valmiina
- // lisätään lisää funktioita:
- class LatestDataView @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
- ) : LinearLayout(context, attrs, defStyleAttr) {
- // rest of the basic methods here from the template above
- init {
- // asetetaan LinearLayout ylhäältä alas (oletus horizontal)
- this.orientation = VERTICAL
- }
- // apufunktio, jonka kautta esim. fragment voi lisätä uuden
- // TextViewin / viestin listaan
- fun addData(message: String) {
- }
- }
- // lisätään ulkoasuun testimielessä (muista clean project ja rebuild project jos ei meinaa toimia aluksi)
- // lisätään myös nappi testaamista varten
- <com.example.android2023tv.LatestDataView
- android:id="@+id/latestDataView_tester"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="30dp" />
- <Button
- android:id="@+id/button_add_data_test"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="ADD DATA" />
- // kokeillaan fragmentin koodissa lisätä satunnainen teksti listaan
- // lisätään joku random string listaan
- binding.buttonAddDataTest.setOnClickListener {
- var text = UUID.randomUUID().toString()
- binding.latestDataViewTester.addData(text)
- }
- // tehdään drawable-kansioon uusi tiedosto: customborder.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <padding android:left="4dp" android:right="4dp" android:top="4dp" android:bottom="4dp"/>
- <stroke android:width="4dp" android:color="#6ECA45"/>
- </shape>
- asetetaan LatestDataViewiin taustakuvaksi XML:ssä:
- <com.example.android2023tv.LatestDataView
- android:id="@+id/latestDataView_tester"
- android:background="@drawable/customborder"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="30dp" />
- // estetään ettei listaan tule yli 5 TextViewiä:
- // maksimirivien määrä listassa
- val maxRows = 5
- // rest of the basic methods here from the template above
- init {
- // asetetaan LinearLayout ylhäältä alas (oletus horizontal)
- this.orientation = VERTICAL
- }
- // apufunktio, jonka kautta esim. fragment voi lisätä uuden
- // TextViewin / viestin listaan
- fun addData(message: String) {
- // niin kauan kuin listassa on liikaa TextViewejä:
- // poista vanhin TextView
- while(this.childCount >= maxRows) {
- this.removeViewAt(0)
- }
- // lisätään uusi TextView listaan
- var newTextView : TextView = TextView(context) as TextView
- newTextView.setText(message)
- newTextView.setBackgroundColor(Color.BLACK)
- newTextView.setTextColor(Color.YELLOW)
- this.addView(newTextView)
- }
- // jos halutaan että LatestDataViewillä on oikea korkeus heti alussa (5x TextView + borderin korkeus), voidaan tehdä näin:
- // rest of the basic methods here from the template above
- init {
- // asetetaan LinearLayout ylhäältä alas (oletus horizontal)
- this.orientation = VERTICAL
- // jotta LatestDataView ei olisi heti alussa läjässä (koska korkeus on 0)
- // muutetaan LatestDataViewin korkeus koodin avulla viiden TextViewin korkuiseksi
- // tehdään yksi TextView muistiin (tätä ei laiteta mihinkään näkyville, vaan tästä lasketaan
- // yhden TextViewin korkeus TÄMÄN PUHELIMEN NÄYTÖLLÄ
- var someTextView : TextView = TextView(context)
- // pyydetään Androidia mittaamaan tämän TextViewin koko, JOS SE OLISI näytöllä
- // jos tämä jätetään pois, muistissa olevan TextViewin korkeus on silloin vain 0
- someTextView.measure(0,0)
- // yhden TextViewin korkeus on siten tämä
- var rowHeight = someTextView.measuredHeight
- // mitataan myös itse LinearLayout (esim. borderin takia)
- this.measure(0,0)
- // LinearLayoutin alkukorkeus on siten tämä (borderin takia)
- var additionalHeight = this.measuredHeight
- // asetetaan LinearLayoutilla aloituskorkeus, johon mahtuu tasan viisi TextViewiä
- // eli kaava = yhden TextViewin korkeus * 5 + LinearLayoutin korkeus alussa
- this.minimumHeight = rowHeight * maxRows + additionalHeight
- }
- // kokeillaan myös simppeliä fade-in animaatiota. tee projektin res-kansioon uusi kansio:
- anim
- ja tee sinne uusi tiedosto
- customfade.xml
- sisällöksi (käytännössä kytketään alpha-kanavaan 2sek animaatio, joka nostaa viewin näkyvyydeen 10%:sta 100%:iin):
- <?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>
- // Kotlinissa pitää ottaa vielä käyttöön tämä animaatio
- // juuri ennen kuin TextView lisätään näkyviin:
- // haetaan res -> anim -> customfade.xml animaatio
- // ja kytketään se TextViewiin -> käynnistetään animaatio
- val fadeAnimation : Animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
- newTextView.startAnimation(fadeAnimation)
- // tässä vaiheessa komponentti on aika pitkälle valmis, voidaan kytketä esim. myös säädataan:
- // tehdään viesti, aikaleima + lämpötila + humidity
- var time = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
- var message = "${time} - Temperature: ${temperature}℃, humidity: ${humidity}%"
- // varmistetaan tällä se, että binding-layeria käsitellään varmasti UI-threadista
- // joissain tapauksissa MQTT-koodi ajetaan tausta-threadissa, jolloin binding-layerin
- // käyttäminen voi tiltata
- activity?.runOnUiThread {
- binding.latestDataViewTemperatures.addData(message)
- }
- // frame-animaatiot, demo: robotti
- drawable kansioon uusi tiedosto: robotanimation (kuvatiedostot tehtävänannon linkistä):
- <?xml version="1.0" encoding="utf-8"?>
- <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <item
- android:duration="100"
- android:drawable="@drawable/idle1"/>
- <item
- android:duration="100"
- android:drawable="@drawable/idle2"/>
- .... jne ....
- </animation-list>
- // lisätään johonkin fragmentiin ImageView:
- <ImageView
- android:id="@+id/imageview_robot"
- android:layout_width="220dp"
- android:layout_height="400dp"
- android:layout_marginBottom="30dp"
- android:background="@drawable/robotanimation"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
- // Kotlinissa:
- // käynnistetään animaatio
- val mImageViewFilling: ImageView = binding.imageviewRobot
- (mImageViewFilling.background as AnimationDrawable).start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement