Fragment
(碎片/片段),在Android 3.0
被引入,主要为了给大屏幕上更加动态、灵活的UI设计提供支持。
Fragment基本使用
创建Fragment
使用Fragment
,需要创建一个类继承Fragment
。
如果使用support v4
包下提供的Fragment
,需要注意:
-
Activity
需要继承FragmentActivity
- 需通过
getSupportFragmentManager()
来获取FragmentManager
class TestFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_test, container, false)
}
}
如果Fragment
是有界面的,那么必须实现onCreateView()
生命周期方法,并在其中利用LayoutInflater
返回所需要显示的View
。
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
参数说明:
-
resource
为你所需要展示布局的ID -
root
为所需要扩展布局的父布局ViewGroup
-
attachToRoot
表示是否将所需扩展布局添加到root
中。因为系统最后会将onCreateView()
返回的View
添加到root
中,所以在此处设置为false
,避免重复添加导致java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
异常错误。
添加Fragment
将创建好的Fragment
添加到Activity
中,有两种方式:静态添加和动态添加。
静态添加
在Activity
布局文件中直接使用
添加:
在
标签的class
属性设置具体的Fragment
,就完成了将Fragment
静态添加到Activity
中。
动态添加
在Activity
运行期间,使用FragmentTransaction
相应方法将Fragment
动态添加到Activity
中:
supportFragmentManager.beginTransaction()
.add(R.id.frameLayout, TestFragment())
.commit()
add()
方法第一个参数是ViewGroup
的id
,告知FragmentManager
片段应该被放置的位置,同时id
也是此Fragment
的唯一标识,可以通过FragmentManager
的findFragmentById()
查找。
通信方式
setArguments()
在实例化Fragment
时,可以通过setArguments()
方法,利用Bundle
来传递参数。所以通常我们会在Fragment
中创建一个newInstance
方法,告诉使用者需要传递哪些参数:
class TestFragment : Fragment() {
companion object {
fun newInstance(name: String, bgColor: Int): TestFragment {
val fragment = TestFragment()
val bundle = Bundle()
bundle.putString("name", name)
bundle.putInt("bgColor", bgColor)
fragment.arguments = bundle
return fragment
}
}
}
在newInstance
方法中处理数据传递,实例化Fragment
并将其返回。
getActivity()
在Fragment
中我们可以通过getActivity()
方法获取Activity
实例,从而使用Activity
相应方法。
值得注意的是:
-
getActivity()
有可能返回的是null
,在使用过程中最好进行相应的判断(例如:fragment.isAdd()
、getActivity() != null
等)。 - 最好不要在
Fragment
中长期持有宿主Activity
的引用,处理不当,很有可能导致宿主Activity
不能正常回收,导致内存泄露。
使用接口
Fragment
的一些操作想要告知宿主Activity
,我们可以创建一个回调接口,让Activity
实现:
class TestFragment : Fragment() {
private var name: String = "TestFragment"
private var bgColor: Int = Color.WHITE
private var onSubmitClickListener: OnSubmitClickListener? = null
companion object {
fun newInstance(name: String, bgColor: Int): TestFragment {
val fragment = TestFragment()
val bundle = Bundle()
bundle.putString("name", name)
bundle.putInt("bgColor", bgColor)
fragment.arguments = bundle
return fragment
}
}
interface OnSubmitClickListener {
fun onSubmitClick(result: String)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
onSubmitClickListener = context as OnSubmitClickListener
} catch (e: ClassCastException) {
throw ClassCastException("${(context as Activity)} must implement OnSubmitClickListener")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
name = it.getString("name", "Fragment1")
bgColor = it.getInt("bgColor", Color.WHITE)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_test, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parent?.setBackgroundColor(bgColor)
tv.text = name
btnSubmit.setOnClickListener {
onSubmitClickListener?.onSubmitClick("The submit button is clicked.")
}
}
override fun onDestroy() {
super.onDestroy()
onSubmitClickListener = null
}
}
class MainActivity : AppCompatActivity(), TestFragment.OnSubmitClickListener {
override fun onSubmitClick(result: String) {
Toast.makeText(this, result, Toast.LENGTH_SHORT).show()
}
...
}
startActivityForResult
Fragment
提供了startActivityForResult
用于Activity
跳转与数据回传,查看源码可以发现最终调用的是FragmentActivity
中的startActivityForResult()
。
在FragmentActivity
的onActivityResult()
方法中,根据requestCode
查找到相应的Fragment
,并调用它的onActivityResult()
。所以可以在Fragment
的onActivityResult()
中获取回传数据。
因为Fragment
并没有提供setResult()
,如果是在目标Activity
的Fragment
中处理回传数据,可以使用getActivity().setResult()
。
Fragment懒加载
在Fragment
和ViewPager
结合使用时,我们通常希望当Fragment
显示的时候再加载获取相应数据。而ViewPager
默认会预加载下一页,ViewPager
的setOffscreenPageLimit(int limit)
方法也无法设置小于1的数字,其内部处理了limit
小于1直接取默认值,也就是1。
ViewPager
实际上是通过Fragment
的setUserVisibleHint
方法来实现其页面的展示和隐藏,我们可以利用setUserVisibleHint
和Fragment
相应生命周期及方法,来达到我们想要的效果:
abstract class LazyLoadFragment : Fragment() {
protected var isViewInitiated: Boolean = false
protected var isDataLoaded: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
isViewInitiated = true
prepareRequestData()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
prepareRequestData()
}
abstract fun requestData()
fun prepareRequestData(forceUpdate: Boolean = false): Boolean {
if (userVisibleHint && isViewInitiated && (!isDataLoaded || forceUpdate)) {
requestData()
isDataLoaded = true
return true
}
return false
}
}
想要实现懒加载,只需继承LazyLoadFragment
,实现requestData()
方法即可。
Fragment生命周期
Fragment
依附于Activity
,Fragment
的生命周期与Activity
的生命周期息息相关。
下面贴上一张来自GitHub上的 xxv/android-lifecycle
项目里的图:
注意:
- 使用
FragmentTransaction
的hide()
、show()
方法隐藏和显示Fragment
,并不会触发Fragment
的生命周期方法。可在onHiddenChanged()
方法中进行监听其变化。- 使用
FragmentTransaction
的replace()
方法,会导致同一容器中之前添加的所有Fragment
被销毁,依次执行onPause
->onStop
->onDestroyView
->onDestroy
->onDetach
。
参考链接
- Android Fragment 的使用,一些你不可不知的注意事项
- Android Fragment+ViewPager 组合,一些你不可不知的注意事项
- Fragment全解析系列(一):那些年踩过的坑
- Android Fragment 你应该知道的一切
- 官方文档