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)

AndroidBanner - ViewPager 03的更多相关文章

  1. 自定义控件(视图)2期笔记03:自定义控件之使用系统控件(优酷案例之广告条Viewpager)

    1.首先我们看看运行效果,如下: 2. 下面就是详细实现这个效果的过程: (1)新建一个Android工程,命名为"广告条的效果",如下: (2)这里用到一个控件ViewPager ...

  2. 【Android UI】案例03滑动切换效果的实现(ViewPager)

    本例使用ViewPager实现滑动切换的效果.本例涉及的ViewPager.为android.support.v4.view.ViewPager.所以须要在android项目中导入android-su ...

  3. Android使用Fragment来实现ViewPager的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信

    以下内容为原创,转载请注明:http://www.cnblogs.com/tiantianbyconan/p/3364728.html 我前两天写过一篇博客<Android使用Fragment来 ...

  4. android 解决ViewPager双层嵌套的滑动问题

    解决ViewPager双层嵌套的滑动问题 今天我分享一下ViewPager的双层嵌套时影响内部ViewPager的触摸滑动问题 之前在做自己的一个项目的时候,遇到广告栏图片动态切换,我第一时间想到的就 ...

  5. Universal-Image-Loader解析(三)——用ListView和ViewPager加载网络中的图片

           现在我们终于可以通过这个框架来实现ListView中加载图片了,至于ViewPager还是别的,原理其实都是一样的 一.ListView 1.布局文件 list_layout.xml & ...

  6. 在Tabbed Activity(ViewPager)中切换Fragment

    我用Android Studio的向导新建了一个Tabbed Activity,里面是ViewPager样式的,有三个tabs.如下: 但是我尝试在第一个tab中设置一个按钮,打开其他tab的时候,却 ...

  7. 关于fragment+viewpager的优化

    上次写了一个问答项目,用的fragment+viewpager架构,后来发现,划了几次之后,再划回来,会重新加载布局,重新获取数据,这样整个程序和卡,并且占用太多的网络资源. 当时的解决办法是,自己重 ...

  8. 踩石行动:ViewPager无限轮播的坑

    2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...

  9. Android ViewPager打造3D画廊

    本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This cl ...

  10. Android中Fragment和ViewPager那点事儿(仿微信APP)

    在之前的博文<Android中使用ViewPager实现屏幕页面切换和引导页效果实现>和<Android中Fragment的两种创建方式>以及<Android中Fragm ...

随机推荐

  1. 后台调用 WEBAPI 几种方式

    示例是调用谷歌短网址的API. 1. HttpClient方式: public static async void DoAsyncPost() { DateTime dateBegin = DateT ...

  2. vue导出文件

    /**导出 */ async toExcel() { // let result = await this.axios({ // method: 'get', // url: `issdc-manag ...

  3. win10 wampserver升级 php7.0至 php7.2

    1.去官网下载php7.2 下载地址: https://windows.php.net/download#php-7.0 2.下载安装 visual c++ 2017 或  visual c++ 20 ...

  4. ReSharp的安装和使用教程

    1.ReSharp的安装及破解: (1)不多说,直接上下载链接: 链接:https://pan.baidu.com/s/1cJmTQxDS-OHmHs46Q_hbMg 提取码:shiz (2)下载好解 ...

  5. 面向对象的练习总结(java)

    三次作业总结博客 l  前言 第一次题目集是我刚刚接触java所做的第一套习题,本次题目难度不大,题量较多,涉及的知识点主要是基础的语法知识,出题人的意图是让我们尽快熟悉java的语法,由于事先有c语 ...

  6. 20193314白晨阳《Python程序设计》实验四 Python综合实践

    课程:<Python程序设计> 班级: 1933 姓名: 白晨阳 学号:20193314 实验教师:王志强老师 实验日期:2021年6月13日 必修/选修: 公选课 实验内容: Pytho ...

  7. Python机器学习/LogisticRegression(逻辑回归模型)(附源码)

    LogisticRegression(逻辑回归) 逻辑回归虽然名称上带回归,但实际上它属于监督学习中的分类算法. 1.算法基础 LogisticRegression基本架构源自于Adline算法,只是 ...

  8. DBCC大全集之(适用版本MS SQLServer 2008 R2)----DBCC SHRINKDATABASE收缩指定数据库中的数据文件和日志文件的大小

    收缩指定数据库中的数据文件和日志文件的大小.  Transact-SQL 语法约定 语法 DBCC SHRINKDATABASE ( database_name | database_id | 0 [ ...

  9. Cplex解决JSP问题

    我的上一篇博客Cplex解决FSP问题 - 加油,陌生人! - 博客园 (cnblogs.com)用Cplex完成了FSP的建模,这篇文章主要是解决JSP问题(车间调度问题). JSP问题:n个工件, ...

  10. c# Visual Studio|There is no editor available for ***,make sure the application for the file type(.vb) is installed问题解决方法

    这个问题出现在在使用VS编码当中,电脑意外关机,导致的文件的缺失或者损坏. 使用反编译软件(如:ILSpy)对编译后的 .EXE文件进行反编译,在翻遍的结果中将相关代码拷贝至目标路径下,替换所需文件. ...