我正在制作一个简单的应用程序,其中包含一个计时器列表,并允许用户通过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
}
}
答案 0 :(得分:2)
看起来您正在使用 ViewModel
通过适配器和查看传呼机为onTouchEvent(ev: MotionEvent?)
进行通信,如果您想使适配器和视图寻呼机独立于ViewModel,我建议您采用以下方式:
使一个接口具有方法onSwipeDown()
和onSwipeUp()
(或为ViewModel制作任意N个方法)
使用此接口对象代替视图分页器和适配器中的ViewModel
。
将此接口实施到您的ViewModel
(它将显示ViewModel中的接口方法)。
将您的界面投射到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()
})
}