書225頁——Fragment的最佳實戰:一個簡易版的新聞應用
項目說明
Fragment的產生是爲了更好的適應平板界面。練習內容,主要是在手機和平板端分別展示不同的頁面。以新聞爲例,分爲標題部分和內容兩部分,在平板上MainActivity直接加載兩個Fragment,在手機上需要兩個Activity分別加載兩個Fragment。爲了讓newsContentFragment達到複用的效果,分別在MainActivity和NewsContentActivity上加載。有關平板與手機加載頁面的是通過新建 layout-sw600dp 實現的。
實現效果
項目結構
遇到問題
有關 NewsContentActivity 中的代碼問題
佈局文件 activity_news_content.xml
<LinearLayout 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=".activity.NewsContentActivity">
<fragment
android:id="@+id/newsContentFrag"
android:name="com.vertex.myapplication.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</fragment>
</LinearLayout>
Activity代碼 NewsContentActivity.kt
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
val fragment = newsContentFrag as NewsContentFragment;
fragment.refresh(title, content)
}
有關書上的寫法是這樣的,但由於無法使用 kotlin-android-extensions 所以我們需要自己去獲取控件。插件內部的邏輯還是findViewById呀,於是···你人才般的延伸了以下代碼:
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
val fragment = findViewById<View>(R.id.newsContentFrag) as NewsContentFragment;
fragment.refresh(title, content)
}
直到項目運行起來報錯:ClassCastException :xxx can not cast NewsContentFragment
細細想來,findViewById 返回的是一個View啊,而NewsContentFragment不是一個View啊。那麼Java裏面是怎麼寫的,哦!Java裏面就沒有這種寫法,是通過FragmentManager 去動態綁定的吧。簡直學費了···
於是,毫不猶豫的寫下以下代碼:
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
val beginTransaction = supportFragmentManager.beginTransaction()
val newsContentFragment = NewsContentFragment();
newsContentFragment.refresh(title, content)
beginTransaction.replace(R.id.newsContentFrag, newsContentFragment)
beginTransaction.commit()
}
重點來了,NewsContentFragment頁面展示出來了,但是數據沒綁定上。回頭看一下 NewsContentFragment 中的代碼
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("TAG", "onCreateView: --------")
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_news_content, container, false).apply {
initView(
this
)
}
return view
}
var newsTitle: TextView? = null
var newsContent: TextView? = null
var contentLayout :LinearLayout? = null
private fun initView(view: View?) {
newsTitle = view?.findViewById<TextView>(R.id.newsTitle)!!
newsContent = view.findViewById<TextView>(R.id.newsContent)
contentLayout = view.findViewById<LinearLayout>(R.id.contentLayout)
}
fun refresh(title: String, content: String) {
contentLayout?.visibility = View.VISIBLE;
newsTitle?.text = title
newsContent?.text = content
}
也是因爲 kotlin-android-extensions 插件不能用的原因,所以我把控件初始化放在 onCreateView 中,refresh 還是保留原來的功能方法。
那麼問題來了,onCreateView 屬於Fragment的生命週期,不是構造函數,不是 new Fragment 就能執行的,那麼直接new Fragment 之後立即調用了refresh 方法,此時控件並沒有初始化啊!
那麼那麼既然這樣,把 newsContentFragment.refresh(title, content) 的調用放在 commit() 的後面,讓 fragment 先綁定上頁面再去調用refresh 綁定數據,豈不完美?
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
val beginTransaction = supportFragmentManager.beginTransaction()
val newsContentFragment = NewsContentFragment();
beginTransaction.replace(R.id.newsContentFrag, newsContentFragment)
beginTransaction.commit()
newsContentFragment.refresh(title, content)
}
但結果並未如常所願,還是先執行了refresh 方法後執行了 initView 方法,我想···beginTransaction.commit() 是開了個線程去幹活吧··· 異步了
折磨着發現,還有一個 commitNow 方法,那 commitNow 會不會同步呢,我看了網上一位同學的源碼分析,commitNow 應該是立即執行,不放在隊列中,但也許···這並不代表着同步吧(我沒再繼續研究)
突然覺得這段代碼有點狗啊···那麼如何處理呢
思路1:做一個延遲,等待fragment執行onCreateView 再調用 refresh
思路2:fragment中通過refresh將title和content 用類變量存儲起來,如果控件不爲空則直接綁定。在initView中添加綁定的方法,如果數據不爲空則進行綁定。
···
不繼續了