AndroidBanner - ViewPager 03
- 当banner不可见的时候,也需要停止轮播
- 给banner设置点击事件,长时间的触摸也会被默认是一个点击事件
这篇文章就来解决这些问题,并处理一下banner的曝光打点问题。
解决banner 不可见依旧轮播的问题
当Banner添加到屏幕上,且对用户可见的时候,可以开始轮播
当Banner从屏幕上移除,或者Banner不可见的时候,可以停止轮播
当手指触摸到Banner时,停止轮播
当手指移开时,开始轮播
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
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