如何复用BottomSheet并绑定对应Fragment的ViewModel?
这个问题的核心是用面向接口编程的思路,把BottomSheet需要的操作抽象成通用接口,让不同的ViewModel都实现这个接口,这样BottomSheet就不用绑定具体的ViewModel了,自然就能彻底避免冗余代码。下面一步步教你怎么改:
先创建一个接口,把BottomSheet里需要调用的所有方法都放进去——也就是你现在XML里绑定的oneSelected()和twoSelected():
interface BottomSheetSelectionHandler { fun oneSelected() fun twoSelected() }
修改你的ViewModelA和ViewModelB,让它们都实现上面的接口,保留各自原本的业务逻辑就行:
// ViewModelA的修改 class ViewModelA : ViewModel(), BottomSheetSelectionHandler { override fun oneSelected() { // 保留你原来的实现逻辑 } override fun twoSelected() { // 保留你原来的实现逻辑 } // ViewModelA原本的其他方法不受影响 } // ViewModelB的修改 class ViewModelB : ViewModel(), BottomSheetSelectionHandler { override fun oneSelected() { // 保留你原来的实现逻辑 } override fun twoSelected() { // 保留你原来的实现逻辑 } // ViewModelB原本的其他方法不受影响 }
把布局里的viewModel变量类型换成刚才定义的接口,这样不管是哪个ViewModel,只要实现了接口就能绑定:
<?xml version="1.0" encoding="utf-8"?> <layout> <data> <!-- 把具体的ViewModel类型换成通用接口 --> <variable name="selectionHandler" type="com.yourpackage.BottomSheetSelectionHandler" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{()->selectionHandler.oneSelected()}" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{()->selectionHandler.twoSelected()}" /> </LinearLayout> </layout>
(这里把变量名改成selectionHandler是为了语义更清晰,当然你也可以保留viewModel,只要类型是接口就行)
让BottomSheet依赖这个通用接口,而不是具体的ViewModel。获取宿主Fragment的ViewModel后,直接转成接口类型即可:
class BottomSheetSample : BottomSheetDialogFragment() { // 通过参数接收宿主Fragment的导航图ID(如果不同Fragment属于不同导航图) private val navGraphId by lazy { arguments?.getInt("NAV_GRAPH_ID") ?: throw IllegalArgumentException("必须传入导航图ID") } // 获取导航图对应的ViewModel,泛型指定为接口类型 private val selectionHandler: BottomSheetSelectionHandler by navGraphViewModels(navGraphId) { viewModelFactory } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val binding: BottomSheetSampleBinding = DataBindingUtil.inflate( inflater, R.layout.bottom_sheet_sample, container, false ) binding.selectionHandler = selectionHandler binding.lifecycleOwner = viewLifecycleOwner return binding.root } // 静态方法方便外部调用传参 companion object { fun newInstance(navGraphId: Int) = BottomSheetSample().apply { arguments = Bundle().apply { putInt("NAV_GRAPH_ID", navGraphId) } } } }
现在不管是FragmentA还是FragmentB,都可以直接调用同一个BottomSheetSample,完全不需要再创建重复的BottomSheet类:
// 在FragmentA中显示BottomSheet BottomSheetSample.newInstance(R.id.nav_A_graph) .show(parentFragmentManager, "BottomSheetSample") // 在FragmentB中显示BottomSheet BottomSheetSample.newInstance(R.id.nav_B_graph) .show(parentFragmentManager, "BottomSheetSample")
这种方式的优势非常明显:后续不管你要在多少个Fragment中复用这个BottomSheet,只要对应的ViewModel实现了BottomSheetSelectionHandler接口,就能直接复用,完全不会产生冗余代码。如果以后需要给BottomSheet加新按钮,只需要在接口里新增方法,再让ViewModel实现即可,扩展性极强。
内容的提问来源于stack exchange,提问作者hushed_voice

