Fragment的场景使用

440 阅读5分钟

一、概述

为了在项目中更好的运用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。