一、概述
为了在项目中更好的运用Fragment,管理Fragment的各种状态。以下会介绍实际开发会遇到的一些场景
二、Fragment与Activity数据通信
- a、如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
- b、如果Activity中未保存任何Fragment的引用,可以通过每个Fragment都有一个唯一的TAG或者ID,在Activity中使用getFragmentManager()的findFragmentByTag()或findFragmentById()获得任何Fragment实例,然后访问Fragment的public方法。
- c、在Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作。
如果在Fragment中需要获取Context,可以调用getActivity(),
如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()获取
推荐方式:
1.接口(Fragment返回数据给Activity)
Fragment部分代码:
class TestFragment : Fragment() {
public lateinit var onResult: (data: String) -> Unit
...
fun initListener() {
btn_mock_data.setOnClickListener {
if (::onResult.isInitialized) {
onResult.invoke("Fragment传递数据到宿主Activity")
}
}
}
}
Activity部分代码:
fun initView() {
val fragment = TestFragment()
supportFragmentManager.beginTransaction().add(R.id.fragment_container, fragment).commit()
fragment.onResult = { data ->
tv_fragment_data.text = data
}
}
2.Fragment Arguments(Activity传递数据到Fragment中)
class TestFragment : Fragment() {
private var mArgument: String? = null
companion object {
const val KEY_ARGUMENT = "KEY_ARGUMENT"
fun newInstance(argument: String): TestFragment {
val bundle = Bundle()
bundle.putString(KEY_ARGUMENT, argument)
val f = TestFragment()
f.arguments = bundle
return f
}
}
...
initData() {
if (arguments != null) {
mArgument = arguments!!.getString(KEY_ARGUMENT) ?: ""
}
}
}
setArguments方法必须在fragment创建以后,添加给Activity前完成。千万不要先调用了add,然后设置arguments。
三、Fragment重叠问题处理
当屏幕旋转或者内存重启(Fragment以及容器Activity被系统回收后再打开时重新初始化)会导致Fragment重叠问题,是因为Activity本身重启的时候会恢复Fragment,然后创建的Fragment代码又会新建一个Fragment的原因。
解决方法:在onCreate方法中判断添加的Fragment是否已存在,若不存在添加Fragment,若存在不进行操作。通过的findFragmentByTag进行判断,所以添加Fragment时需要用标记TAG
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (supportFragmentManager.findFragmentByTag("TestFragment") == null) {
val fragment = TestFragment()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment,"TestFragment").commit()
}
}
四、Fragment的回退栈
Activity是由任务栈管理的,遵循先进后出的原则,Fragment也可以实现类似的栈管理,从而实现多个Fragment先后添加后可以返回上一个Fragment,当Activity容器内没有Fragment时回退则退出Activity。
主要方法FragmentTransaction.addToBackStack(String) // 通常传入null即可
代码如下:
val fragment = BackStackFragment()
supportFragmentManager.beginTransaction().add(R.id.fragment_container, fragment).addToBackStack(null).commit()
注:
1.ctivity的第一个Fragment(根Fragment)可以不添加回退栈,这样最后一个Fragment按返回时就不会出现Activity空白的情况,而是直接退出Activity。
2.调用addToBackStack(null)将当前的事务添加到了回退栈,调用replace方法后Fragment实例不会被销毁,但是视图层次会被销毁,即会调用onDestoryView和onCreateView。
若需保存当前Fragment视图状态,则可以使用hide后add新的Fragment
五、DialogFragment的使用
和普通Fragment拥有一致的生命周期。且DialogFragment也允许开发者把Dialog作为内嵌的组件进行重用,类似Fragment(可以在大屏幕和小屏幕显示出不同的效果)。
使用DialogFragment至少需要实现onCreateView或者onCreateDIalog方法。
- onCreateView使用定义的xml布局文件展示Dialog。
- onCreateDialog利用AlertDialog或者Dialog创建Dialog。
a.创建一个对话框布局文件:
b.继承DialogFragment,重写onCreateView方法 or onCreateDialog方法:
class MyDialogFragment : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// 隐藏对话框标题栏
dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
return inflater.inflate(R.layout.fragment_dialog, container)
}
...
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setView(LayoutInflater.from(activity).inflate(R.layout.fragment_dialog,null))
.setPositiveButton("OK") { dialog, which ->}
.setNegativeButton("Cancel") { dialog, which ->}
return builder.create()
}
}
c.在Activity中调用:
val dialogFragment = MyDialogFragment()
dialogFragment.show(supportFragmentManager,"DialogFragment")
六、Fragment—保存大量数据
主要用于处理异步请求带来的数据保存问题,尤其是异步请求未完成时屏幕旋转这种现象。步骤如下:
- 1、继承Fragment,声明引用指向你的有状态的对象
- 2、当Fragment创建时调用setRetainInstance(boolean)
- 3、把Fragment实例添加到Activity中
- 4、当Activity重新启动后,使用FragmentManager对Fragment进行恢复
七、Fragment的startActivityForResult
在Fragment中存在startActivityForResult()以及onActivityResult()方法,
在启动的Activity中设置setResult(Fragment.REQUEST_CODE, intent)来设置返回。
Fragment代码:
class TestStartActivityForResultFragment : Fragment() {
private var mArgument: String? = null
companion object {
const val AC_REQUEST_CODE = 0x01
fun newInstance(argument: String): TestStartActivityForResultFragment {
val bundle = Bundle()
bundle.putString(KEY_ARGUMENT, argument)
val f = TestStartActivityForResultFragment()
f.arguments = bundle
return f
}
}
。。。
fun initListener() {
btn_start_activity_for_result.setOnClickListener {
//启动Activity。不能使用activity.startActivityForResult调用。否则不会回到fragment onActivityResult方法
startActivityForResult(Intent(activity, StartForResultActivity::class.java), AC_REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.i("123", "Fragment onActivityResult")
if (requestCode == AC_REQUEST_CODE) {
tv_result_data.text = data?.getStringExtra("data")
}
}
override fun initData() {
if (arguments != null) {
mArgument = arguments!!.getString(KEY_ARGUMENT) ?: ""
}
}
}
Activity代码:
val intent = Intent()
intent.putExtra("data", "setResult返回数据")
setResult(TestStartActivityForResultFragment.AC_REQUEST_CODE, intent)
finish()
八、Fragment间的数据传递
调用Fragment.setTargetFragment ,该方法一般用于当前fragment由其它fragment启动时。
接收回调数据Fragment:
class TestFragmentFragment : Fragment() {
...
fun initListener() {
btn_fragment_fragment.setOnClickListener {
val dialogFragment = MyDialogFragment()
//需要setTargetFragment
dialogFragment.setTargetFragment(this, MyDialogFragment.REQUEST_CODE)
dialogFragment.show(activity!!.supportFragmentManager, "DialogFragment")
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MyDialogFragment.REQUEST_CODE) {
tv_result_data.text = data?.getStringExtra("data2")
}
}
}
返回数据DialogFragment:
class MyDialogFragment : DialogFragment() {
companion object{
const val REQUEST_CODE = 0x02
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder
.setView(LayoutInflater.from(activity).inflate(R.layout.fragment_dialog,null))
.setPositiveButton("OK") { dialog, which ->
setResult()
}.setNegativeButton("Cancel") { dialog, which ->
}
return builder.create()
}
// 设置模拟返回数据
protected fun setResult() {
// 判断是否设置了targetFragment
if (targetFragment == null)
return
val intent = Intent()
intent.putExtra("data2", "DialogFragment数据返回");
targetFragment!!.onActivityResult(REQUEST_CODE, Activity.RESULT_OK, intent)
}
九、FragmentPagerAdapter与FragmentStatePagerAdapter的区别
使用ViewPager再结合上面任何一个实例的制作APP主页,主要区别就在与对于fragment是否销毁:
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底将fragment从当前Activity的FragmentManager中移除,
state表明销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
如上所说,使用FragmentStatePagerAdapter更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。