最近正在开发公司的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)
}
}
效果如下: