第一種做法
因爲在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} %"
}
}
最後可以看到頁面滑走再滑回來,不會閃退,資料也會更新回來