Android 通过FragmentManager传递数据

1,025 阅读2分钟

最近正在开发公司的App,Fragment是开发流程中经常会使用的组件。这几天刚好碰上个功能,需要从DialogFragment把值返回给Fragment。之前有使用过EventBus、接口传值等方式,但是都不太方便。 找了下官方的实现方案,发现可以通过FragmentManager来传递数据。

官方文档传送门

简介

从 Fragment 1.3.0-alpha04 开始,每个 FragmentManager 都会实现 FragmentResultOwner。这意味着 FragmentManager 可以充当Fragment结果的集中存储区。此更改通过设置 Fragment结果并监听这些结果,而不要求Fragment直接引用彼此,让单独的Fragment相互通信。

如何使用

在需要接收数据的FragmentActivity或者Fragment注册监听,代码如下:

//在FragmentActivity中
supportFragmentManager.setFragmentResultListener("requestKey", lifecycleOwner) { requestKey, result ->
}

//在Fragment中
parentFragmentManager.setFragmentResultListener("requestKey", lifecycleOwner) { requestKey, result ->
}

//父级Fragment和子级Fragment之间传递结果时,在父级Fragment中
childFragmentManager.setFragmentResultListener("requestKey", lifecycleOwner) { requestKey, result ->
}

在传递数据的Fragment中设置数据,代码如下:

//requestKey必须与注册监听时的相同
parentFragmentManager.setFragmentResult(”requestKey“, bundle)

可以通过如下代码清除结果和监听:

parentFragmentManager.clearFragmentResult(”requestKey“)
parentFragmentManager.clearFragmentResultListener(”requestKey“)

对于同一个requestKey来说,只能有一个监听器和一个结果,如果先后多次调用setFragmentResult(),监听器只会接收到最后一次调用时设置的result。设置了监听器的Fragment或者FragmentActvity,只有在onStarted之后才会接收到回调。

示例

做了一个演示的demo,包含了父级子级Fragment之间传递数据,同级Fragment之间传递数据,完整代码如下:

const val TAG = "FragmentResultAPI"

class FragmentResultApiActivity : FragmentActivity() {

    private val canonicalName = this::class.java.canonicalName!!

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<LayoutFragmentResultApiActivityBinding>(this, R.layout.layout_fragment_result_api_activity)

        supportFragmentManager.setFragmentResultListener(canonicalName, this) { requestKey, result ->
            Log.i(TAG, "Activity receive result requestKey:$requestKey ,result:$result")
            binding.tvReceiver.text = "Activity receive: requestKey = $requestKey ,result = $result"
        }

        binding.btnShowDialog.setOnClickListener {
            DialogFragment().show(supportFragmentManager, null)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        supportFragmentManager.clearFragmentResultListener(canonicalName)
    }
}

class DialogFragment : DialogFragment() {

    private var binding: LayoutFragmentResultApiDialogFragmentBinding? = null

    private val canonicalName = this::class.java.canonicalName!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        dialog?.window?.run {
            setBackgroundDrawable(ContextCompat.getDrawable(requireContext(), android.R.color.transparent))
            decorView.setBackgroundResource(android.R.color.transparent)

            val layoutParams = attributes
            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
            layoutParams.gravity = Gravity.CENTER
            attributes = layoutParams
        }
        binding = DataBindingUtil.inflate(inflater, R.layout.layout_fragment_result_api_dialog_fragment, container, false)
        return binding?.root
    }

    @SuppressLint("SetTextI18n")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        childFragmentManager.setFragmentResultListener(canonicalName, this) { requestKey, result ->
            Log.i(TAG, "Dialog Fragment receive result requestKey:$requestKey ,result:$result")
            binding?.tvReceiver?.text = "Dialog Fragment receive: requestKey = $requestKey ,result = $result"
            val bundle = Bundle()
            bundle.putString("result", "DialogFragment transit ${result.getString("result", "")}")
            parentFragmentManager.setFragmentResult(FragmentResultApiActivity::class.java.canonicalName!!, bundle)
        }
        binding?.run {
            btnAFragment.setOnClickListener {
                vpContainer.currentItem = 0
            }
            btnBFragment.setOnClickListener {
                vpContainer.currentItem = 1
            }

            val fragments = ArrayList<Class<out Fragment?>>()
            fragments.add(FragmentA::class.java)
            fragments.add(FragmentB::class.java)

            vpContainer.adapter = ViewPager2Adapter(this@DialogFragment, fragments)
            vpContainer.isUserInputEnabled = false

            vpContainer.currentItem = 0
        }
    }

    override fun onDestroyView() {
        binding?.unbind()
        childFragmentManager.clearFragmentResultListener(canonicalName)
        super.onDestroyView()
    }
}

class ViewPager2Adapter(fragment: Fragment, private val fragments: ArrayList<Class<out Fragment?>>) : FragmentStateAdapter(fragment) {

    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position].newInstance()!!
    }
}

class FragmentA : Fragment() {

    lateinit var binding: LayoutFragmentResultApiFragmentABinding

    private val canonicalName = this::class.java.canonicalName!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = DataBindingUtil.inflate(inflater, R.layout.layout_fragment_result_api_fragment_a, container, false)
        return binding.root
    }

    @SuppressLint("SetTextI18n")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        parentFragmentManager.setFragmentResultListener(canonicalName, this) { requestKey, result ->
            Log.i(TAG, "A Fragment receive result requestKey:$requestKey ,result:$result")
            binding.tvReceiver.text = "A Fragment receive: requestKey = $requestKey ,result = $result"
        }
        binding.btnSendParent.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("result", "a result from Fragment A")
            parentFragmentManager.setFragmentResult(DialogFragment::class.java.canonicalName!!, bundle)
        }
        binding.btnSendToB.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("result", "a result from Fragment A")
            parentFragmentManager.setFragmentResult(FragmentB::class.java.canonicalName!!, bundle)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        parentFragmentManager.clearFragmentResultListener(canonicalName)
    }
}

class FragmentB : Fragment() {

    lateinit var binding: LayoutFragmentResultApiFragmentBBinding

    private val canonicalName = this::class.java.canonicalName!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = DataBindingUtil.inflate(inflater, R.layout.layout_fragment_result_api_fragment_b, container, false)
        return binding.root
    }

    @SuppressLint("SetTextI18n")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        parentFragmentManager.setFragmentResultListener(canonicalName, this) { requestKey, result ->
            Log.i(TAG, "B Fragment receive result requestKey:$requestKey ,result:$result")
            binding.tvReceiver.text = "B Fragment receive: requestKey = $requestKey ,result = $result"
        }
        binding.btnSendToA.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("result", "a result from Fragment B")
            parentFragmentManager.setFragmentResult(FragmentA::class.java.canonicalName!!, bundle)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        parentFragmentManager.clearFragmentResultListener(canonicalName)
    }
}

效果如下:

1653729035322895_.gif