第一種做法
因爲在viewpager2的adapter有fragment的實例了
所以要做的就是當在MainActivity送出搜尋時
各fragment要有方法可以update
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu, menu)
val searchItem = menu?.findItem(R.id.action_search)
if (searchItem != null) {
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
if (newText!!.isNotEmpty()) {
weatherList.forEach { location ->
if (location.locationName.contains(newText)) {
(viewPager2.adapter as PageAdapter).fragments.forEach {
if (it is FirstFragment) it.updateContent(location)
else if (it is SecondFragment) it.updateContent(location)
}
}
}
}
return true
}
})
}
return super.onCreateOptionsMenu(menu)
}
重點在這段,主要表示將PageAdapter的fragments(這是list)全部掃過一遍
然後將搜尋的location內容傳入fragment中的updateContent方法
(viewPager2.adapter as PageAdapter).fragments.forEach {
if (it is FirstFragment) it.updateContent(location)
else if (it is SecondFragment) it.updateContent(location)
}
viewPager2.adapter as PageAdapter
的意思是
因爲viewPager2.adapter
回傳是RecyclerView.AdapterPageAdapter
繼承FragmentStateAdapter
而FragmentStateAdapter
也繼承自RecyclerView.Adapter
又因爲fragments是PageAdapter的屬性
所以將viewPager2.adapter
轉型爲PageAdapter
分解寫的意思就是
但寫成這樣就簡潔有力
這樣viewPager2.adapter
就能取得fragments,再對其進行forEach了
if (it is FirstFragment) it.updateContent(location)
else if (it is SecondFragment) it.updateContent(location)
在裡面的這二行,it表示forEach讀到的fragment
編譯器也有提示(淺灰色)
if (it is FirstFragment) it.updateContent(location)
若forEach讀到的fragment(第一個it)是FirstFragment,就呼叫FirstFragment(第二個it)的updateContent
else if (it is SecondFragment) it.updateContent(location)
依此類推
若forEach讀到的fragment(第一個it)是SecondFragment,就呼叫SecondFragment(第二個it)的updateContent
參考
https://www.kotlincn.net/docs/reference/typecasts.html
再來就是分別在FirstFragment與SecondFragment撰寫updateContent()
FirstFragment
class FirstFragment : Fragment() {
...
override fun onCreate(savedInstanceState: Bundle?) {}
...
fun updateContent(location: WeatherData.Records.Location){
val calendar = Calendar.getInstance()
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
tv_city.text = location.locationName
tv_currentTime.text =
(month + 1).toString() + "月" + day.toString() + "日" + SimpleDateFormat(" HH:mm").format(
System.currentTimeMillis()
)
//當日
tv_startTime1.text = location.weatherElement[0].time[0].startTime
tv_endTime1.text =
location.weatherElement[0].time[0].endTime//.split(" ")[1]
tv_status1.text =
location.weatherElement[0].time[0].parameter.parameterName
tv_rainProbability1.text =
"降雨機率 ${location.weatherElement[1].time[0].parameter.parameterName} %"
tv_startTime2.text = location.weatherElement[0].time[1].startTime
tv_endTime2.text =
location.weatherElement[0].time[1].endTime//.split(" ")[1]
tv_status2.text =
location.weatherElement[0].time[1].parameter.parameterName
tv_rainProbability2.text =
"降雨機率 ${location.weatherElement[1].time[1].parameter.parameterName} %"
}
}
這樣執行到MainActivity的搜尋if (it is FirstFragment) it.updateContent(location)
時
就會把搜尋到的location資料作爲引數,並呼叫FirstFragment的updateContent方法
就可把資料給各textview進行畫面更新
但是,又有但是了
還有其它重點須要注意,例如fragment的生命週期
多fragment時,假設搜尋後更新了當前畫面資料(FirstFragment),然後滑動到其它fragment
有可能FirstFragment被Destroy後,再滑回來FirstFragment
FirstFragment重新onCreate以後,畫面就是預設的,並沒有資料
如下圖,搜尋臺南後,FirstFragment有資料了,多滑動幾頁後再回來
FirstFragment被Destroy再重新Create後,就沒有資料了
要用一些方式改善
因爲當fragment被移除是進入到onDestroyView的lifecycle
先前顯示的資料都會清除
試想..如果fragment被帶回時,會進入onCreatView的lifecycle
那在onCreatView()再次呼叫updateContent方法就可以再顯示資料啦
不過要注意先前搜尋時,是將搜尋取得的WeatherData.Records.Location,作爲引數location再呼叫updateContent方法
而在onCreatView()再次呼叫updateContent方法的話,也是須要再把剛剛搜尋的WeatherData.Records.Location,作爲引數傳入
但現在是在fragment自己的onCreatView()呼叫了,所以須要在開始MainActivity的搜尋時
就要在fragment設置變數記錄這個搜尋的資料,這樣fragment自己呼叫updateContent方法時,就可以拿自己記錄的這個變數傳入
如下:
fragment持有一個fragmentLocation的變數,記錄來自MainActivity的搜尋(location)
fun updateContent(location: WeatherData.Records.Location){
fragmentLocation = location
...
}
fragment就要先宣告fragmentLocation變數
因爲有可能還沒有取得搜尋,所以此變數是可空的
也必須先賦值爲null
class FirstFragment : Fragment() {
var fragmentLocation: WeatherData.Records.Location? = null
}
再來就是到onCreateView()呼叫updateContent並將fragmentLocation傳入
看到編譯器有提示錯誤(type mismatch)
因爲在fragment宣告fragmentLocation是可空變數
而傳入updateContent的不能是可空變數
最先建立updateContent方法的時候也不會設定讓可空變數傳入
因爲一定是有搜尋資料要更新畫面內容才會呼叫updateContent
按下該行前面的紅色燈泡,可以看到編譯器提示可用?.let{}的方式
正確如下,檢查當fragmentLocation非空時
就執行updateContent(),並將此fragmentLocation(it)傳入
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
fragmentLocation?.let { updateContent(it) }
...
}
run看看
預期應該是搜尋完之後,滑到其它fragment,再滑回來時,FirstFragment再次更新資料並顯示
結果不是像之前一樣沒更新,反而是閃退??
看報錯的log
爲什麼說tv_city must not be null
fragment在onCreateViewinflate的時候已經有回傳inflater.inflate(R.layout.fragment_first, container, false)了
tv_city等的view應該都有建立了,哪裏錯了?
原來當畫面onDestroyView再create後,雖然textView的id還是叫tv_city,但已經不是先前的tv_city
而updateContent可能還要對先前的tv_city更新,但先前的tv_city已經不在了,所以會出錯
解決方式就是明確的告訴updateContent是對現在所inflate的view更新
設置一個rootView變數來持有現在inflate的layout
class FirstFragment : Fragment() {
...
var rootView: View? = null
...
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
rootView = inflater.inflate(R.layout.fragment_first, container, false)
fragmentLocation?.let { updateContent(it) }
return rootView
}
...
}
然後修改updateContent,當rootView非空時,對現在的這個rootView(it)做更新
fun updateContent(location: WeatherData.Records.Location){
fragmentLocation = location
rootView?.let {
// rootView不爲空執行
val calendar = Calendar.getInstance()
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
it.tv_city.text = location.locationName
it.tv_currentTime.text =
(month + 1).toString() + "月" + day.toString() + "日" + SimpleDateFormat(" HH:mm").format(
System.currentTimeMillis()
)
//當日
it.tv_startTime1.text = location.weatherElement[0].time[0].startTime
it.tv_endTime1.text =
location.weatherElement[0].time[0].endTime//.split(" ")[1]
it.tv_status1.text =
location.weatherElement[0].time[0].parameter.parameterName
it.tv_rainProbability1.text =
"降雨機率 ${location.weatherElement[1].time[0].parameter.parameterName} %"
it.tv_startTime2.text = location.weatherElement[0].time[1].startTime
it.tv_endTime2.text =
location.weatherElement[0].time[1].endTime//.split(" ")[1]
it.tv_status2.text =
location.weatherElement[0].time[1].parameter.parameterName
it.tv_rainProbability2.text =
"降雨機率 ${location.weatherElement[1].time[1].parameter.parameterName} %"
}
}
最後可以看到頁面滑走再滑回來,不會閃退,資料也會更新回來