将ViewModel与ViewPagers结合

时间:2018-10-01 17:05:19

标签: android android-viewpager android-architecture-components android-viewmodel

我正在制作一个简单的应用程序,其中包含一个计时器列表,并允许用户通过ViewPager在这些计时器之间滚动。 Android Architecture Component原则在这里有效:ViewModel保留应用程序的状态,而Fragments / Activities仅处理UI交互。

在这种情况下,已创建了自定义ViewPager和FragmentStatePagerAdapter。我的问题是,让ViewPager /适配器响应ViewModel中的更改的最佳方法是什么?

下面的当前代码可以正常工作,但是有更好的方法吗?当前,ViewPager和适配器都需要引用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
    }
}

2 个答案:

答案 0 :(得分:2)

看起来您正在使用 ViewModel 通过适配器查看传呼机onTouchEvent(ev: MotionEvent?)进行通信,如果您想使适配器和视图寻呼机独立于ViewModel,我建议您采用以下方式:

  1. 使一个接口具有方法onSwipeDown()onSwipeUp() (或为ViewModel制作任意N个方法)

  2. 使用此接口对象代替视图分页器和适配器中的ViewModel

  3. 将此接口实施到您的ViewModel (它将显示ViewModel中的接口方法)

  4. 将您的界面投射到ViewModel上,在该界面中将ViewModel传递给适配器并查看分页器。

Voila!您已将适配器和视图传呼器设置为独立于ViewModel的直接引用。

答案 1 :(得分:1)

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


ViewPager: 注意,我们现在有了一个Interface对象。滑动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()
        }
    }
}

活动实施:

    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()
    })
}