Сочетание ViewModels с ViewPager - PullRequest
0 голосов
/ 01 октября 2018

Я делаю простое приложение, которое содержит список таймеров и позволяет пользователю прокручивать эти таймеры через ViewPager.Здесь действуют принципы Компонента архитектуры Android: ViewModel хранит состояние приложения, а Fragments / Activities просто обрабатывают взаимодействия с пользовательским интерфейсом.

В этом случае были созданы пользовательские ViewPager и FragmentStatePagerAdapter.Мой вопрос: Каков наилучший способ заставить ViewPager / адаптер реагировать на изменения в 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
    }
}

Ответы [ 2 ]

0 голосов
/ 01 октября 2018

Ответ от Jeel Vankhede был отмечен как решение и реализован.Для дальнейшего использования, код решения будет вставлен ниже, чтобы показать особенности того, как ViewPager и адаптер были успешно сделаны независимо от 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()
        }
    }
}

Осуществление деятельности:

    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()
    })
}
0 голосов
/ 01 октября 2018

Похоже, вы используете ViewModel для связи по onTouchEvent(ev: MotionEvent?) с вашего адаптера & просмотра пейджера , если вы хотите сделать свой адаптер& просмотр независимой от пейджера формы ViewModel. Я бы посоветовал вам следующий способ:

  1. Сделать один интерфейс с методами onSwipeDown() & onSwipeUp() (или создать любое N чисел метода, который вы хотитеиметь для ViewModel)

  2. Возьмите этот объект интерфейса вместо ViewModel в вашем пейджере и адаптере.

  3. Реализуйте этоинтерфейс к вашему ViewModel (который предоставит методы интерфейса во ViewModel) .

  4. Приведите ваш интерфейс к ViewModel, где вы передаете ViewModel адаптеруи просмотреть пейджер.

Вуаля!вы сделали свой адаптер и пейджер независимыми от прямой ссылки ViewModel.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...