今天來寫點關於ViewModel儲存狀態模組
ViewModel雖然現在網路上有很多人寫關於這部分的東西,也有很多實例幫助我們了解ViewModel
不過我有段時間其實一直不明白他跟螢幕旋轉中的儲存狀態(onSaveInstanceState)跟這個ViewModel到底差在哪裡
直到...呃,好吧我工作也沒有碰到要用ViewModel,就是有天吃飽太閒再看文檔的時候才好像稍微有了解了那麼億點點?
總之,我個人是看到了一個重點。也就是他的生命週期的部分
有木有看到旁邊那一大條綠綠的ViewModelScope? 沒錯,這個意思很明顯就是
ViewModel凌駕於生命週期之上!!(喂別亂下定論)( ͡ಠ ʖ̯ ͡ಠ)
好啦其實這個定論是正確的.....
恩,其實在Android官方文檔就有提到,ViewModel就是最為一個「可儲存任何東西」的存在
並且這個存在是凌駕於生命週期之上
因此在官方範例中,他舉了很經典的「螢幕旋轉」做為範例來解釋
而我呢?當然也沒那麼聰明地也拿螢幕旋轉來解釋啦XDDD
阿,說到螢幕旋轉也可以看看我對於螢幕旋轉的文章喔!
->碼農日常-『Android studio』關於Android手機螢幕旋轉的一些大小事
好,事不宜遲,我們開始今天的範例
還有Github
-> https://github.com/thumbb13555/AndroidBlogExamples/tree/main/ViewModelExample
開始吧!
1. 不好意思再廢話個幾句
恩...這段一樣式廢話啦
不過看了GIF之後大概也知道了今天的主題是做一個碼表程式
那其實我當初在考量要如何寫ViewModel的時候,我懂了是懂了,不過我真正想做的事情是
寫出一篇能夠清楚點出使用ViewModel與使用onSaveInstanceState兩者之間的差別
畢竟兩者都是螢幕旋轉,如果都是螢幕旋轉的話,那用ViewModel無非是脫褲子放屁或者是殺雞焉用牛刀看你尬藝哪個就用哪個(笑)
最早我認識ViewModel時是看這個範例
->https://qiita.com/KIRIN3qiita/items/7d833e2c010c0b2c02d9
(抱歉了,我住日本所以Google都是優先丟給我日文的文章)
這位大大的文章用了一種極為簡單的方式使我很快上手了解了ViewModel,我非常感謝
不過他的範例有一個尷尬的地方,那就是他的範例用ViewModel與使用onSaveInstanceState兩者去寫都看不出差異
也因此後來我想了良久,想到了在使用像碼表紀錄實時變數的東西,是經不起需要「螢幕旋轉」中間的延遲的
什麼意思?意思是說其實Android手機在螢幕旋轉時,都會有那麼幾毫秒的Lag
而這個Lag雖然看起來無傷大雅,但是如果紀錄像是以豪秒為單位的變數時,這個豪秒恐怕會影響到紀錄的準確性
因此為了克服這個問題,我想到如果把執行緒都寫在ViewModel內的話,便不會有這種問題了吧!
所以當初在選擇題材的時候,其實也是下了點功夫去思考的
OK大致就是這樣,總之開始吧!
2. 介面&撰寫ViewModel
首先介面,沒什麼好提的,直接丟
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text_stopwatch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0:00:00" android:textColor="@color/black" android:textSize="32dp" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.6" /> <Button android:id="@+id/button_stopwatch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" /> </androidx.constraintlayout.widget.ConstraintLayout>
好,接著來到重點。就是寫ViewModel的部分
首先我的專案文件長這樣,請直接new 一個Java Class
然後讓他繼承ViewModel
最後,我在裡面寫一些需要用到的組件,直接全給吧!
public class StopwatchModel extends ViewModel { private int nanoSeconds = 0; private boolean isRunning = false; private String time = "0:00:00"; private CallBack callBack; public void setCallBack(CallBack callBack) { this.callBack = callBack; } private Handler handler = new Handler(); private Runnable r = new Runnable() { @Override public void run() { int min = nanoSeconds / 3600; int sec = (nanoSeconds % 3600) / 60; int nano = nanoSeconds % 60; time = String.format(Locale.getDefault(), "%d:%02d:%02d", min, sec, nano); callBack.callbackListener(time); nanoSeconds++; handler.postDelayed(this, 10); } }; public void stopwatchSwitch(){ if (!isRunning) { handler.post(r); isRunning = true; } else { handler.removeCallbacks(r); isRunning = false; nanoSeconds = 0; } } interface CallBack{ void callbackListener(String time); } }
首先是紅底白字部分,handler+runnable的一系列非同步執行這種東西對於第一次接觸的人有點小複雜,不過基本上快要是一個Set了XD
而我這裡也剛好也有寫過這部分的介紹,想延伸了解的話可以看看
->碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法
再來是黃底白字部分其實也很簡單,就是一個Callback。使這邊跑的資訊可以傳給MainActivity那邊使用
大意就是如此,其他就是一些開跑與停止的方法而已,不再贅述
3. 使用模組
最後就是使用我們寫的這塊模組,一樣先全給
public class MainActivity extends AppCompatActivity { private TextView tvStopwatch; private StopwatchModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvStopwatch = findViewById(R.id.text_stopwatch); Button btStopwatch = findViewById(R.id.button_stopwatch); viewModel = new ViewModelProvider(this) .get(StopwatchModel.class); viewModel.setCallBack(time -> tvStopwatch.setText(time)); btStopwatch.setOnClickListener(view ->viewModel.stopwatchSwitch()); } }
一個重點,初始化ViewModel
沒了(笑)
下面的部分的話則是callback以及取用方法開啟/結束計時
大概就是如此~
這篇文章我自己在寫的時候也覺得有點草草的
不過這當時在寫Code的時候,還是花了點時間操考一下別人的碼表通常怎麼寫
然後再試著把他加入到ViewModel這樣,也就是雖然本文短,不過寫扣花的時間稍微比較多
希望有幫助到大家囉~
最後....
留言列表