代码之家  ›  专栏  ›  技术社区  ›  Josh Ribeiro

组合ViewModels和ViewPagers

  •  1
  • Josh Ribeiro  · 技术社区  · 6 年前

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

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

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

    可视寻呼机

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

    碎片雷达

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

        private fun setupPagerAdapter() {
        val pagerAdapter = TimerSlidePagerAdapter(viewModel = viewModel, fragmentManager = supportFragmentManager)
        with(pager){
            adapter = pagerAdapter
            homeViewModel = viewModel
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  2
  •   Jeel Vankhede    6 年前

    看起来你在用 ViewModel 沟通 onTouchEvent(ev: MotionEvent?) 从你的 适配器 & 查看寻呼机

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

    2. 视图模型

    3. 将此接口实现到 视图模型 (它将公开ViewModel中的接口方法)

    4. 将ViewModel传递给适配器和视图寻呼机的位置。

    瞧!您已经制作了适配器&独立于ViewModel的直接引用的视图寻呼机。

        2
  •  1
  •   Josh Ribeiro    6 年前

    来自Jeel Vankhede的答案被标记为一个解决方案并付诸实施。为供将来参考,解决方案代码将粘贴在下面,以显示ViewPager和适配器如何成功独立于ViewModel的细节:


    查看寻呼机: 注意,我们现在有了一个接口对象。滑动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()
        })
    }