tuomasvaltanen

Untitled

Mar 27th, 2023 (edited)
242
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.90 KB | None | 0 0
  1. // Edistynyt mobiiliohjelmointi, 27.3.2023
  2.  
  3. // tehdään aluksi oma Kotlin-luokka custom viewiä varten:
  4.  
  5. class CustomTemperatureView @JvmOverloads constructor(
  6. context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
  7. ) : View(context, attrs, defStyleAttr) {
  8.  
  9. init
  10. {
  11. // this is constructor of your component
  12. // all initializations go here
  13. }
  14.  
  15. override fun onDraw(canvas: Canvas) {
  16. super.onDraw(canvas)
  17.  
  18. // here you can do all the drawing
  19. }
  20.  
  21. override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int){
  22. super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  23.  
  24. // Android uses this to determine the exact size of your component on screen
  25. }
  26.  
  27. }
  28.  
  29. // tehdään erillinen fragment Custom View -komponenttien testaamiselle, esim. CustomViewTestFragment:
  30.  
  31. class CustomViewTestFragment : Fragment() {
  32. // change this to match your fragment name
  33. private var _binding: FragmentCustomViewTestBinding? = null
  34.  
  35. // This property is only valid between onCreateView and
  36. // onDestroyView.
  37. private val binding get() = _binding!!
  38.  
  39. override fun onCreateView(
  40. inflater: LayoutInflater,
  41. container: ViewGroup?,
  42. savedInstanceState: Bundle?
  43. ): View? {
  44. _binding = FragmentCustomViewTestBinding.inflate(inflater, container, false)
  45. val root: View = binding.root
  46.  
  47.  
  48.  
  49. // the binding -object allows you to access views in the layout, textviews etc.
  50.  
  51. return root
  52. }
  53.  
  54. override fun onDestroyView() {
  55. super.onDestroyView()
  56. _binding = null
  57. }
  58. }
  59.  
  60. // CustomViewTesterFragmentin ulkoasuun LinearLayout testaamisen helpottamiseksi:
  61.  
  62. <?xml version="1.0" encoding="utf-8"?>
  63. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  64. xmlns:tools="http://schemas.android.com/tools"
  65. android:layout_width="match_parent"
  66. android:layout_height="match_parent"
  67. android:orientation="vertical"
  68. android:layout_margin="5dp"
  69. tools:context=".CustomViewTestFragment">
  70.  
  71.  
  72.  
  73. </LinearLayout>
  74.  
  75. // oman custom viewin lisääminen on kuin lisäisi minkä tahansa muun viewin ulkoasuun:
  76.  
  77. <?xml version="1.0" encoding="utf-8"?>
  78. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  79. xmlns:tools="http://schemas.android.com/tools"
  80. android:layout_width="match_parent"
  81. android:layout_height="match_parent"
  82. android:orientation="vertical"
  83. android:layout_margin="5dp"
  84. tools:context=".CustomViewTestFragment">
  85.  
  86. <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
  87. android:layout_width="wrap_content"
  88. android:layout_height="wrap_content" />
  89.  
  90. </LinearLayout>
  91.  
  92. // piirretään lisää tavaraa
  93. class CustomTemperatureView @JvmOverloads constructor(
  94. context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
  95. ) : View(context, attrs, defStyleAttr) {
  96.  
  97. private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
  98. private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  99.  
  100. init
  101. {
  102. // define the colors!
  103. paint.color = Color.BLUE
  104. textPaint.color = Color.WHITE
  105. textPaint.textSize = 75f
  106. textPaint.textAlign = Paint.Align.CENTER
  107. textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
  108. }
  109.  
  110. // drawing is typically done in the custom view's onDraw-method
  111. override fun onDraw(canvas: Canvas) {
  112. super.onDraw(canvas)
  113.  
  114. // you can do all the drawing through the canvas-object
  115. // parameters: x-coordinate, y-coordinate, size, color
  116. canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
  117.  
  118. // parameters: content, x, y, color. little offset to y-axis to bump it to center
  119. canvas.drawText("Test!", width.toFloat() / 2, width.toFloat() / 2 + 30, textPaint);
  120. }
  121.  
  122. var size : Int = 200
  123.  
  124. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  125. // Try for a width based on our minimum
  126. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
  127. var w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1)
  128.  
  129. // if no exact size given (either dp or match_parent)
  130. // use this one instead as default (wrap_content)
  131. if (w == 0)
  132. {
  133. w = size * 2
  134. }
  135.  
  136. // Whatever the width ends up being, ask for a height that would let the view
  137. // get as big as it can
  138. // val minh: Int = View.MeasureSpec.getSize(w) + paddingBottom + paddingTop
  139. // in this case, we use the height the same as our width, since it's a circle
  140. val h: Int = View.resolveSizeAndState(
  141. View.MeasureSpec.getSize(w),
  142. heightMeasureSpec,
  143. 0
  144. )
  145.  
  146. setMeasuredDimension(w, h)
  147. }
  148.  
  149. }
  150.  
  151. // asetetaan nyt kooksi 120x120dp
  152.  
  153. <?xml version="1.0" encoding="utf-8"?>
  154. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  155. xmlns:tools="http://schemas.android.com/tools"
  156. android:layout_width="match_parent"
  157. android:layout_height="match_parent"
  158. android:orientation="vertical"
  159. android:layout_margin="5dp"
  160. tools:context=".CustomViewTestFragment">
  161.  
  162. <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
  163. android:layout_width="120dp"
  164. android:layout_height="120dp" />
  165.  
  166. </LinearLayout>
  167.  
  168. // viimeinen versio customviewistä:
  169.  
  170. class CustomTemperatureView @JvmOverloads constructor(
  171. context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
  172. ) : View(context, attrs, defStyleAttr) {
  173.  
  174. private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
  175. private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  176.  
  177. // konstruktori
  178. init
  179. {
  180. // define the colors and sizes!
  181. paint.color = Color.BLUE
  182. textPaint.color = Color.WHITE
  183. textPaint.textSize = 80f
  184. textPaint.textAlign = Paint.Align.CENTER
  185. textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
  186. }
  187.  
  188. // tämä muuttuja pitää kirjaa aktiivisesta lämpötilasta
  189. private var temperature : Int = 0
  190.  
  191. // muutetaan lämpötilaa fragmentin kautta
  192. fun changeTemperature(temp : Int) {
  193. temperature = temp
  194.  
  195. // tee tämä tehtävänannon mukaisesti viidelle eri värille
  196. if(temperature > 0) {
  197. paint.color = Color.RED
  198. }
  199. else {
  200. paint.color = Color.BLUE
  201. }
  202.  
  203. // tuli muutos dataan, ilmoitetaan Androidille
  204. // että pitää piirtä komponentti uudestaan
  205. invalidate()
  206. requestLayout()
  207. }
  208.  
  209. // drawing is typically done in the custom view's onDraw-method
  210. override fun onDraw(canvas: Canvas) {
  211. super.onDraw(canvas)
  212.  
  213. // you can do all the drawing through the canvas-object
  214. // parameters: x-coordinate, y-coordinate, size, color
  215. canvas.drawCircle(width.toFloat() / 2, width.toFloat() / 2, width.toFloat() / 2, paint)
  216.  
  217. // parameters: content, x, y, color. little offset to y-axis to bump it to center
  218. canvas.drawText("${temperature}℃", width.toFloat() / 2, width.toFloat() / 2 + 30, textPaint);
  219. }
  220.  
  221. // default view size if nothing is given in layout (wrap_content)
  222. var size : Int = 200
  223.  
  224. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  225. // Try for a width based on our minimum
  226. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
  227. var w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1)
  228.  
  229. // if no exact size given (either dp or match_parent)
  230. // use this one instead as default (wrap_content)
  231. if (w == 0)
  232. {
  233. w = size * 2
  234. }
  235.  
  236. // Whatever the width ends up being, ask for a height that would let the view
  237. // get as big as it can
  238. // val minh: Int = View.MeasureSpec.getSize(w) + paddingBottom + paddingTop
  239. // in this case, we use the height the same as our width, since it's a circle
  240. val h: Int = View.resolveSizeAndState(
  241. View.MeasureSpec.getSize(w),
  242. heightMeasureSpec,
  243. 0
  244. )
  245.  
  246. setMeasuredDimension(w, h)
  247. }
  248.  
  249. }
  250.  
  251. // jos haluat testata CustomViewTestFragmentissa että changeTemperature-funktio toimii:
  252.  
  253. // testataan että lämpötilaa voidaan vaihtaa
  254. // jos lämpötila ei vaihdu, tarkista että
  255. // changeTemperaturen viimeisinä rivinä on invalidate() ja requestLayout()
  256. binding.customtemperatureviewTest.changeTemperature(5)
  257.  
  258. binding.buttonChangeTemperature.setOnClickListener {
  259. binding.customtemperatureviewTest.changeTemperature(-10)
  260.  
  261. }
  262.  
  263. // voidaan kokeilla myös kytketä MQTT:ssä kiinni olevaan sääasemaan, esim ulkoasu:
  264.  
  265. <com.example.edistynytmobiiliohjelmointi2023b.CustomTemperatureView
  266. android:id="@+id/customtemperatureview_weatherstation"
  267. android:layout_width="120dp"
  268. android:layout_height="120dp"
  269. app:layout_constraintEnd_toEndOf="parent"
  270. app:layout_constraintStart_toStartOf="parent"
  271. app:layout_constraintTop_toBottomOf="@+id/speedView" />
  272.  
  273.  
  274. // ajetaan binding-layeria koskevat asia UI-threadissa erikseen
  275. activity?.runOnUiThread(java.lang.Runnable {
  276.  
  277. binding.customtemperatureviewWeatherstation.changeTemperature(temperature.toInt())
  278. })
  279.  
  280.  
  281. ######################################################
  282. Custom viewit, osa 2 : Compound control
  283. ######################################################
  284.  
  285. Tehdään uusi Kotlin-luokka, LatestDataView.kt:
  286.  
  287. class LatestDataView @JvmOverloads constructor(
  288. context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
  289. ) : LinearLayout(context, attrs, defStyleAttr) {
  290.  
  291. // konstruktori, säädetään orientaatio
  292. init {
  293. this.orientation = VERTICAL
  294. }
  295.  
  296. }
  297.  
  298. // lisätään apufunktio jolla voidaan lisätä lennosta uusi TextView
  299.  
  300. class LatestDataView @JvmOverloads constructor(
  301. context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
  302. ) : LinearLayout(context, attrs, defStyleAttr) {
  303.  
  304. // konstruktori, säädetään orientaatio
  305. init {
  306. this.orientation = VERTICAL
  307.  
  308. addData("Testiviesti 1")
  309. addData("Testiviesti 2")
  310. }
  311.  
  312. // this function can be called where it's needed, init() or an Activity.
  313. fun addData(message : String)
  314. {
  315. var newTextView : TextView = TextView(context) as TextView
  316. newTextView.setText(message)
  317. newTextView.setBackgroundColor(Color.BLACK)
  318. newTextView.setTextColor(Color.YELLOW)
  319. this.addView(newTextView)
  320. }
  321.  
  322. }
  323.  
  324. // lisätään LatestDataView fragmenttiin (custom view test fragment)
  325.  
  326. <com.example.edistynytmobiiliohjelmointi2023b.LatestDataView
  327. android:id="@+id/latestdataview_test"
  328. android:layout_width="match_parent"
  329. android:layout_height="wrap_content" />
  330.  
  331. <Button
  332. android:id="@+id/button_add_message"
  333. android:layout_width="match_parent"
  334. android:layout_height="wrap_content"
  335. android:text="ADD DATA" />
  336.  
  337. // jos halutaan kokeilla lisätä viestejä Fragmentissa:
  338.  
  339. // lisätään satunnainen testiviesti
  340. binding.buttonAddMessage.setOnClickListener {
  341. var text = UUID.randomUUID().toString()
  342. binding.latestdataviewTest.addData(text)
  343. }
  344.  
  345. // tehdään customborder.xml drawable-kansioon:
  346.  
  347. <?xml version="1.0" encoding="utf-8"?>
  348. <shape xmlns:android="http://schemas.android.com/apk/res/android"
  349. android:shape="rectangle">
  350. <stroke
  351. android:width="5dp"
  352. android:color="#42f560" />
  353. </shape>
  354.  
  355. // otetaan käyttöön ulkoasussa, tarvitaan paddingia jotta taustakuva tulee esille
  356. // huomaa: borderin leveys ja padding täsmää
  357. <com.example.edistynytmobiiliohjelmointi2023b.LatestDataView
  358. android:id="@+id/latestdataview_test"
  359. android:background="@drawable/customborder"
  360. android:padding="5dp"
  361. android:layout_width="match_parent"
  362. android:layout_height="wrap_content" />
  363.  
  364. // vain 5 riviä sallitaan TextViewejä kerralla:
  365.  
  366. // maksimimäärä rivejä LatestDataViewissä on 5 riviä
  367. var maxRows : Int = 5
  368.  
  369. // this function can be called where it's needed, init() or an Activity.
  370. fun addData(message : String)
  371. {
  372. // poistetaan vanhin viesti että näkyy vain max 5 riviä
  373. while(this.childCount >= maxRows) {
  374. this.removeViewAt(0)
  375. }
  376.  
  377. // luodaan uusi textview lennosta ja laitetaan linearlayoutiin
  378. var newTextView : TextView = TextView(context) as TextView
  379. newTextView.setText(message)
  380. newTextView.setBackgroundColor(Color.BLACK)
  381. newTextView.setTextColor(Color.YELLOW)
  382. this.addView(newTextView)
  383.  
  384. // animoidaan fade in -animaatio
  385. val animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
  386. newTextView.startAnimation(animation)
  387. }
  388.  
  389. // pientä hienosäätöä, jotta LatestDataView on heti alussa oikean korkuinen:
  390.  
  391. init {
  392. this.orientation = VERTICAL
  393.  
  394. // idea: luodaan ohjelman muistiin yksi textview, ja otetaan sen koko talteen
  395. // asetetaan LatestDataViewin alkukorkeudeuksi peruskorkeus + viiden TextViewin yhteenlaskettu korkeus
  396. var someTextView : TextView = TextView(context)
  397.  
  398. // pyydetään Androidia mittaamaan tämän komponentin koko,
  399. // jos se nyt laitettaisiin tämän puhelimen näytölle esille
  400. someTextView.measure(0,0)
  401.  
  402. // lasketaan viiden textviewin tarvitsema pystytila
  403. var additionalHeight = someTextView.measuredHeight * maxRows
  404.  
  405. // mitataan myös itse LinearLayoutin alkukoko
  406. this.measure(0, 0)
  407. var startHeight = this.measuredHeight
  408.  
  409. // asetetaan laskennallinen korkeus, johon mahtuu tasan viisi textviewiä
  410. this.minimumHeight = startHeight + additionalHeight
  411. }
  412.  
  413.  
  414. // tehdään res-kansioon uusi kansio -> anim
  415. // lisätään customfade.xml anim-kansioon:
  416.  
  417. <?xml version="1.0" encoding="utf-8"?>
  418. <set xmlns:android="http://schemas.android.com/apk/res/android"
  419. android:interpolator="@android:anim/linear_interpolator">
  420. <alpha
  421. android:duration="2000"
  422. android:fromAlpha="0.1"
  423. android:toAlpha="1.0">
  424. </alpha>
  425. </set>
  426.  
  427. // lisätään addData()-funktion loppuun animaation käyttöönotto uudelle TextViewille:
  428.  
  429. // animoidaan fade in -animaatio
  430. val animation = AnimationUtils.loadAnimation(context, R.anim.customfade)
  431. newTextView.startAnimation(animation)
  432.  
  433.  
  434. // kokeillaan kytkeä MQTT-sääasemaan:
  435.  
  436. // haetaan aikaleima ja rakennetaan viesti LatestDataViewiin
  437. val timeStamp: String = SimpleDateFormat("HH:mm:ss").format(Date())
  438. var message = "${timeStamp} - Temperature: ${temperature}℃, humidity: ${item.d.get3().v.toInt()}%"
  439.  
  440. // ajetaan binding-layeria koskevat asia UI-threadissa erikseen
  441. activity?.runOnUiThread(java.lang.Runnable {
  442. binding.latestdataviewWeatherstation.addData(message)
  443. })
  444.  
  445.  
  446. // frame by frame animaatiot:
  447. https://bignerdranch.com/blog/frame-animations-in-android/
  448.  
  449. // tehdään esim. robotanimation.xml drawable -kansioon, lisätään framet kuvatiedostoina projektiin (myös drawable-kansioon)
  450.  
  451. // animaation käynnistäminen, esim. CustomViewTestFragment:
  452.  
  453.  
  454. binding.imageviewAnimated.setBackgroundResource(R.drawable.robotanimation)
  455. (binding.imageviewAnimated.getBackground() as AnimationDrawable).start()
  456.  
  457. // XML:ssä pitää olla ImageView:
  458.  
  459. <ImageView
  460. android:id="@+id/imageview_animated"
  461. android:layout_width="200dp"
  462. android:layout_height="wrap_content"
  463. android:background="@drawable/idle1" />
Advertisement
Add Comment
Please, Sign In to add comment