书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中添加绑定的方法,如果数据不为空则进行绑定。
···
不继续了