• 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()  
      
    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{  
        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)

  • 相关阅读:
    4 JavaScript
    关于训练时,最后一轮batch_size=1的报错
    MySql数据库允许外网访问连接
    风起乌兰察布,中国自动驾驶迎来170倍提速
    论文阅读【4】Product-based Neural Networks for User Response Prediction
    ZTree自定义不可展开节点的折叠图标
    2015-2023_个人工作总结
    阿里云k8s环境下,因slb限额导致的发布事故
    计算机体系结构:编译器预取例题(优化前后失效次数计算)
    处理非线性分类的 SVM一种新方法(Matlab代码实现)
  • 原文地址:https://www.cnblogs.com/android-lol/p/17299104.html