将 ViewModel 与 ViewPagers 相结合

Combining ViewModels With ViewPagers

我正在制作一个简单的应用程序,它包含一个计时器列表并允许用户通过 ViewPager 在这些计时器之间滚动。 Android 架构组件原则在这里生效:ViewModel 保存应用程序的状态,Fragments/Activities 只处理 UI 交互。

在这种情况下,自定义的ViewPager 和FragmentStatePagerAdapter 已经制作完成。我的问题是,让 ViewPager/adapter 响应 ViewModel 中的变化的最佳方式是什么?

下面的当前代码工作正常,但是有更好的方法吗?目前,ViewPager 和 Adapter 都需要引用 ViewModel,这感觉与 MVVM 应该实现的相反。

ViewPager

    class VerticalTimerViewPager(context: Context, attributeSet: AttributeSet): ViewPager(context, attributeSet){

    var homeViewModel: HomeViewModel?=null

    init {
        setPageTransformer(true, VerticalPageTransformer())
    }


    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {

        ev ?: return false
        val intercepted =  super.onInterceptTouchEvent(swapXYOnMotionEvent(ev))
            swapXYOnMotionEvent(ev)
        return intercepted
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        homeViewModel ?: throwNoViewModelException()
        when(ev?.action){
            MotionEvent.ACTION_DOWN -> homeViewModel?.onSwipeDown()
            MotionEvent.ACTION_UP -> homeViewModel?.onSwipeUp()
        }
        return super.onTouchEvent(swapXYOnMotionEvent(ev ?: return false))
    }

    private fun swapXYOnMotionEvent(motionEvent: MotionEvent): MotionEvent{
        with(motionEvent){
            val newX = (y/height)*width
            val newY = (x/width) * height
            setLocation(newX, newY)
        }
        return motionEvent
    }

    private fun throwNoViewModelException():Boolean{
        throw RuntimeException("${this.javaClass.simpleName}: HomeViewModel must be set by setting the variable homeViewModel")
    }
}

FragmentStatePagerAdapter

 class VerticalTimerViewPager(context: Context, attributeSet: AttributeSet): ViewPager(context, attributeSet){

    var homeViewModel: HomeViewModel?=null

    init {
        setPageTransformer(true, VerticalPageTransformer())
    }


    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {

        ev ?: return false
        val intercepted =  super.onInterceptTouchEvent(swapXYOnMotionEvent(ev))
            swapXYOnMotionEvent(ev)
        return intercepted
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        homeViewModel ?: throwNoViewModelException()
        when(ev?.action){
            MotionEvent.ACTION_DOWN -> homeViewModel?.onSwipeDown()
            MotionEvent.ACTION_UP -> homeViewModel?.onSwipeUp()
        }
        return super.onTouchEvent(swapXYOnMotionEvent(ev ?: return false))
    }

    private fun swapXYOnMotionEvent(motionEvent: MotionEvent): MotionEvent{
        with(motionEvent){
            val newX = (y/height)*width
            val newY = (x/width) * height
            setLocation(newX, newY)
        }
        return motionEvent
    }

    private fun throwNoViewModelException():Boolean{
        throw RuntimeException("${this.javaClass.simpleName}: HomeViewModel must be set by setting the variable homeViewModel")
    }
}

在 Activity onCreate()

中调用的实例化方法
    private fun setupPagerAdapter() {
    val pagerAdapter = TimerSlidePagerAdapter(viewModel = viewModel, fragmentManager = supportFragmentManager)
    with(pager){
        adapter = pagerAdapter
        homeViewModel = viewModel
    }
}

看起来您正在使用 ViewModel 从您的 适配器 & [= 为 onTouchEvent(ev: MotionEvent?) 进行通信32=]view pager,如果你想让你的适配器和view pager独立于ViewModel,我建议你采用以下方式:

  1. 创建一个具有方法的接口 onSwipeDown() & onSwipeUp() (或者为 ViewModel 创建任意 N 个方法)

  2. 在您的视图寻呼机和适配器中使用此接口对象而不是 ViewModel

  3. 将此接口实现到您的 ViewModel (这将在 ViewModel 中公开接口方法).

  4. 将您的界面投射到 ViewModel,您将在此处将 ViewModel 传递给适配器并查看寻呼机。

瞧!您已经使您的适配器和视图寻呼机独立于 ViewModel 的直接引用。

Jeel Vankhede 的回答被标记为解决方案并已实施。为了以后参考,解决方案代码将粘贴在下面,以显示如何成功使 ViewPager 和 Adapter 独立于 ViewModel 的细节:


ViewPager: 注意我们现在有一个接口对象。滑动ViewPager时,如果没有界面对象,则抛出异常。

//Overriding default touch events and swapping x/y coordinates prior to handling
class VerticalTimerViewPager(context: Context, attributeSet: AttributeSet): ViewPager(context, attributeSet){

    var timerViewPagerEventListener: TimerViewPagerEvent? = null

    init {
        setPageTransformer(true, VerticalPageTransformer())
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        ev ?: return false
        val intercepted =  super.onInterceptTouchEvent(swapXYOnMotionEvent(ev))
            swapXYOnMotionEvent(ev)
        return intercepted
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        timerViewPagerEventListener ?: throwInterfaceExeption()
        when(ev?.action){
            MotionEvent.ACTION_DOWN ->  timerViewPagerEventListener?.onViewPagerSwipeDown()
            MotionEvent.ACTION_UP ->    timerViewPagerEventListener?.onViewPagerSwipeUp()
        }
        return super.onTouchEvent(swapXYOnMotionEvent(ev ?: return false))
    }

    private fun throwInterfaceExeption():Boolean {
        throw RuntimeException("${this.javaClass.simpleName}: Parent Activity must implement TimerViewPagerEvent interface" )
    }

    private fun swapXYOnMotionEvent(motionEvent: MotionEvent): MotionEvent{
        with(motionEvent){
            val newX = (y/height)*width
            val newY = (x/width) * height
            setLocation(newX, newY)
        }
        return motionEvent
    }

    interface TimerViewPagerEvent{
        fun onViewPagerSwipeUp()
        fun onViewPagerSwipeDown()
    }
}

适配器:现在获取对象列表(在本例中为计时器)

class TimerSlidePagerAdapter(fragmentManager: FragmentManager, private val timers: List<TimerEntity>?):
        FragmentStatePagerAdapter(fragmentManager){

    override fun getItem(position: Int): Fragment {
        if (count > 0) {
            return makeTimerFragment(position)
        }
        return NoDataFragment()
    }

    override fun getCount(): Int = timers?.size ?: 0

    //Internal Functions
    private fun makeTimerFragment(position: Int): Fragment {
        timers ?: return NoDataFragment()
        val timeInMS = timers[position].timeInMS
        return if (timeInMS > 0) {
            TimerFragment.newInstance(timeInMS)
        } else {
            NoDataFragment()
        }
    }
}

Activity 实现:

    private fun setupPagerAdapter() {
        val pagerAdapter = TimerSlidePagerAdapter(
                fragmentManager = supportFragmentManager,
                timers = viewModel?.timers?.value)

        with(pager){
            adapter = pagerAdapter
            timerViewPagerEventListener = this@MainActivity
        }
    }

private fun observeTimers(){
    viewModel.timers.observe(this, Observer {timerList->
        timerList ?: Log.e(this.javaClass.simpleName, "List of timers is null")
                .also {return@Observer}
        setupPagerAdapter()
    })
}