AndroidBanner – ViewPager 03

AndroidBanner - ViewPager 03

上一篇文章,描述了如何实现自动轮播的,以及手指触摸的时候停止轮播,抬起继续轮播,其实还遗留了一些问题:

  1. 当banner不可见的时候,也需要停止轮播
  2. 给banner设置点击事件,长时间的触摸也会被默认是一个点击事件

这篇文章就来解决这些问题,并处理一下banner的曝光打点问题。

解决banner 不可见依旧轮播的问题

思考一下:什么时候可以轮播,什么时候不可以轮播

当Banner添加到屏幕上,且对用户可见的时候,可以开始轮播
当Banner从屏幕上移除,或者Banner不可见的时候,可以停止轮播
当手指触摸到Banner时,停止轮播
当手指移开时,开始轮播

所以,我们需要知道什么时候View可见,不可见,添加到屏幕上和从屏幕上移除,幸运的是,这些,android都提供了对应的接口来获取。

OnAttachStateChangeListenner

该接口可以通知我们view添加到屏幕上或者从屏幕上被移除,或者可以直接重写view的onAttachedToWindow和onDetachedFromWindow方法

// view提供的接口,可以通过 addOnAttachStateChangeListener 添加舰艇 public interface OnAttachStateChangeListener {   	public void onViewAttachedToWindow(@NonNull View v);   	public void onViewDetachedFromWindow(@NonNull View v);   }  // 复写view的方法 override fun onAttachedToWindow() {       super.onAttachedToWindow()   }      override fun onDetachedFromWindow() {       super.onDetachedFromWindow()   } 

这里我们通过复写方法的方式处理

onVisibilityChanged

view 提供了方法,可以复写该方法,获取到view 的可见性变化

protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {   } 

onWindowVisibilityChanged

view 提供了方法,可以复习该方法,当前widow的可见性发生变化的时候,会调用通知给我们

protected void onWindowVisibilityChanged(@Visibility int visibility) {       if (visibility == VISIBLE) {           initialAwakenScrollBars();       }   } 

我们根据上面的api,可以封装一个接口,来监听View的可见性

VisibleChangeListener

interface VisibleChangeListener {       /**        * view 可见        */       fun onShown()          /**        * view 不可见        */       fun onDismiss()   } 

Banner重写方法,进行调用

override fun onVisibilityChanged(changedView: View, visibility: Int) {       Log.e(TAG, "onVisibilityChanged ${changedView == this}, vis: $visibility")       dispatchVisible(visibility)   }      override fun onWindowVisibilityChanged(visibility: Int) {       super.onWindowVisibilityChanged(visibility)       Log.e(TAG, "onWindowVisibilityChanged $visibility")       dispatchVisible(visibility)   }      override fun onAttachedToWindow() {       super.onAttachedToWindow()       Log.e(TAG, "onAttachedToWindow ")       this.mAttached = true   }      override fun onDetachedFromWindow() {       Log.e(TAG, "onDetachedFromWindow ")       super.onDetachedFromWindow()       this.mAttached = false   }  private fun dispatchVisible(visibility: Int) {       val visible = mAttached && visibility == VISIBLE       if (visible) {           prepareLoop()       } else {           stopLoop()       }       mVisibleChangeListener?.let {           when (visible) {               true -> it.onShown()               else -> it.onDismiss()           }       }   } 

页面滚动时处理banner轮播

滚动监听,如果是scrollview,就监听滚动事件处理即可。如果是listview,recyclerview可以选择监听onscrollstatechanged,更高效。
下面是scrollview的监听处理

mBinding.scrollView.setOnScrollChangeListener(object :OnScrollChangeListener{       override fun onScrollChange(           v: View?,           scrollX: Int,           scrollY: Int,           oldScrollX: Int,           oldScrollY: Int       ) {           val visible = mBinding.vpBanner.getGlobalVisibleRect(Rect())           Log.e(TAG,"banner visible : $visible")           if(visible){               mBinding.vpBanner.startLoop()           }else{               mBinding.vpBanner.stopLoop()           }       }   }) 

点击事件的处理

首先要声明一个点击事件回调接口

interface PageClickListener {       fun onPageClicked(position: Int)   } 

重写banner的onTouch事件,将移动距离小于100,且按压时间小于500ms的事件认为是点击事件

private var mMoved = false   private var mDownX = 0F   private var mDownY = 0F      /**    * 当前事件流结束时,恢复touch处理的相关变量    */   private fun initTouch() {       this.mMoved = false       this.mDownX = 0F       this.mDownY = 0F   }      private fun calculateMoved(x: Float, y: Float, ev: MotionEvent) {       mClickListener?.let {           // 超过500ms(系统默认的时间) 我们认为不是点击事件           if (ev.eventTime - ev.downTime >= 500) {               return           }           // 移动小于阈值我们认为是点击           if (sqrt(((x - mDownX).pow(2) + (y - mDownY).pow(2))) >= MOVE_FLAG) {               return           }           val count = adapter?.count ?: 0           if (count == 0) {               return           }           // 由于我们实现无限轮播的方式是重新设置当前选中的item,这里要将currentItem重新映射回去           val index = when (currentItem) {               in 1..count - 2 -> currentItem - 1               0 -> count - 1               else -> 0           }           it.onPageClicked(index)       }   }      override fun onTouchEvent(ev: MotionEvent?): Boolean {       when (ev?.action) {           MotionEvent.ACTION_DOWN -> {               this.mDownY = ev.y               this.mDownX = ev.x               stopLoop()           }           MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {               val y = ev.y               val x = ev.x               calculateMoved(x, y, ev)               initTouch()               prepareLoop()           }       }       return super.onTouchEvent(ev)   } 

曝光打点的处理

监听page切换,当page变化的时候,从实际展示的数据队列中取出数据进行曝光。

class ExposureHelper(private val list: List<*>, private var last: Int = -1) :       ViewPager.OnPageChangeListener {          private var mStart: AtomicBoolean = AtomicBoolean(false);          override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) =           Unit          override fun onPageSelected(position: Int) {           Log.e(TAG, "$position $last")           if (last >= 0) {               exposure()           }           last = position       }          override fun onPageScrollStateChanged(state: Int) = Unit          /**        * 开始曝光        * @param current Int        */    fun startExposure(current: Int) {           mStart.set(true)           last = current       }          /**        * 停止曝光        */       fun endExposure() {           if (mStart.get()) {               mStart.set(false)               exposure()           }       }          /**        * 实际执行数据上报的处理        */       private fun exposure() {           val data = list[last]           Log.e(TAG, "data:$data")       }          companion object {           private const val TAG = "ExposureHelper"       }   } 

VPAdapter 对外提供实际展示的数据集

private val mData = mutableListOf<T>()      fun setData(data: List<T>) {       mData.clear()       if (this.loop && data.size > 1) {           // 数组组织一下,用来实现无限轮播           mData.add(data[data.size - 1])           mData.addAll(data)           mData.add(data[0])       } else {           mData.addAll(data)       }   }  fun getShowDataList():List<T>{       return mData   } 

在Banner中的配置使用

private var mExposureHelper: ExposureHelper? = null  /**    * 自动轮播    */   fun startLoop() {       if (mLoopHandler == null) {           mLoopHandler = Handler(Looper.getMainLooper()) { message ->               return@Handler when (message.what) {                   LOOP_NEXT -> {                       loopNext()                       true                   }                   else -> false               }           }       }       if (mLoopHandler?.hasMessages(LOOP_NEXT) != true) {           Log.e(TAG, "startLoop")           mLoopHandler?.sendEmptyMessageDelayed(LOOP_NEXT, mLoopDuration)       }       // 开始轮播时开始曝光(可见时会触发轮播)       mExposureHelper?.startExposure(currentItem)   }      fun stopLoop() {       // 停止轮播时结束曝光(不可见时会停止轮播)       mExposureHelper?.endExposure()       mLoopHandler?.removeMessages(LOOP_NEXT)   }  fun bindExposureHelper(exposureHelper: ExposureHelper?) {       mExposureHelper = exposureHelper       mExposureHelper?.let {           addOnPageChangeListener(it)       }       mExposureHelper?.startExposure(currentItem)   } 

代码:huyuqiwolf/Banner (github.com)

发表评论

评论已关闭。

相关文章