设置文本,预留点击接口而已,再接着到列表Fragment的布局,直接一个RecyclerView (fragment_list.xml)
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="150dp" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_list" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
接着自定义ViewHolder,维护一个选中的值,配置LiveData:
class SharedViewModel: ViewModel() { private val mSelectData = MutableLiveData<String>() fun select(data: String) { mSelectData.value = data } fun getSelected() = mSelectData }
再接着把ListFragment也写出来,获取宿主Activity作用域的SharedViewModel实例,点击时更新值:
class ListFragment(data: ArrayList<String>): Fragment(R.layout.fragment_list) { private var mData = data // 定义SharedViewModel变量 private var mModel: SharedViewModel? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 获得宿主Activity作用域内的SharedViewModel实例 mModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java) view.findViewById<RecyclerView>(R.id.rv_list).apply { adapter = ListAdapter(mData).also { it.setOnItemClickListener(object : ListAdapter.ItemClickListener { override fun onItemClick(choose: String) { // 更新ViewModel中的mSelectData mModel?.select(choose) } }) } layoutManager = LinearLayoutManager(activity) } } }
在接着到右侧Fragment,xml里就一个简单的TextView:(fragment_content.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_blue_light"> <TextView android:id="@+id/tv_content" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"/> </LinearLayout>
补齐ContentFragment:
class ContentFragment : Fragment(R.layout.fragment_content) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 获取宿主Activity作用域的SharedViewModel实例,然后监听数据变化 ViewModelProvider(requireActivity()).get(SharedViewModel::class.java).getSelected() .observe(viewLifecycleOwner) { view.findViewById<TextView>(R.id.tv_content).text = "您翻牌了:${it}" } } }
紧着是测试Activity的xml (activity_vm_test.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <FrameLayout android:id="@+id/fly_choose" android:layout_width="wrap_content" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/fly_content" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> </LinearLayout>
最后上测试Activity:
class VMTestActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_vm_test) val mData = arrayListOf("XX","XX","XX","XX","XXX", "XX", "XX", "XX") supportFragmentManager.apply { beginTransaction().replace(R.id.fly_choose, ListFragment(mData)).commit() beginTransaction().replace(R.id.fly_content, ContentFragment()).commit() } } }
别看这里代码好像很多的样子,只是为了便于读者copy运行体验而已,核心就这四点:
- ① 自定义定义ViewModel,里面放通信的数据,提供set()和get()方法;
- ② ViewModelProvider(requireActivity()).get(SharedViewModel::class.java) 获取一个 作用域为宿主Activity 的ViewModel实例;
- ③ 发消息:调用ViewModel实例的set()方法更新数据;
- ④ 收消息:调用ViewModel实例的get()方法获得一个LiveData,然后observe() 监听数据变化。
通过这样的方式,间接实现了跨页面通信,宿主Activity还蒙在鼓里 (无代码入侵),两个Fragment就偷偷完成了py交易,妙啊!!!
④ 作用域可控
上面两个Fragment轻松实现数据共享的例子,得益于ViewModel的 作用域可控,在创建 ViewModelProvider
时注入不同的 ViewModelStoreOwner
来反映作用域。
如果换成传入 Fragment实例本身,作用域就 仅限于此Fragment,当此Fragment销毁时,对应的ViewModel实例也会被销毁。
扩展一下:ViewModel是如何实现作用域可控的?
看一波源码探探原理,先跟下 ViewModelProvider
的构造方法:
初始化了一个Factory和ViewModelStore,先看第一个参数,调用了 owner.getViewModelStore()
此方法返回一个 ViewModelStore
,跟下:
吼,内部 维护一个ViewModel的集合,还提供一个clear()方法,遍历回调ViewModel的clear()方法,并清空集合。
所以包含关系是:ViewModelProvider
→ ViewModelStore
→ ViewModel
接着看第二个参数,根据传入的ViewModelStoreOwner不同,使用不同的工厂实例:
- 判断条件:owner是否为
HasDefaultViewModelProviderFactory
类型? - 是:强转后调用
getDefaultViewModelProviderFactory()
获取一个ViewModelProvider.Factory
实例; - 否:调用
NewInstanceFactory.getInstance()
获取一个NewInstanceFactory
实例;