AndroidBanner - ViewPager 03
AndroidBanner - ViewPager 03
上一篇文章,描述了如何实现自动轮播的,以及手指触摸的时候停止轮播,抬起继续轮播,其实还遗留了一些问题:
- 当banner不可见的时候,也需要停止轮播
- 给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的更多相关文章
- 自定义控件(视图)2期笔记03:自定义控件之使用系统控件(优酷案例之广告条Viewpager)
1.首先我们看看运行效果,如下: 2. 下面就是详细实现这个效果的过程: (1)新建一个Android工程,命名为"广告条的效果",如下: (2)这里用到一个控件ViewPager ...
- 【Android UI】案例03滑动切换效果的实现(ViewPager)
本例使用ViewPager实现滑动切换的效果.本例涉及的ViewPager.为android.support.v4.view.ViewPager.所以须要在android项目中导入android-su ...
- Android使用Fragment来实现ViewPager的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信
以下内容为原创,转载请注明:http://www.cnblogs.com/tiantianbyconan/p/3364728.html 我前两天写过一篇博客<Android使用Fragment来 ...
- android 解决ViewPager双层嵌套的滑动问题
解决ViewPager双层嵌套的滑动问题 今天我分享一下ViewPager的双层嵌套时影响内部ViewPager的触摸滑动问题 之前在做自己的一个项目的时候,遇到广告栏图片动态切换,我第一时间想到的就 ...
- Universal-Image-Loader解析(三)——用ListView和ViewPager加载网络中的图片
现在我们终于可以通过这个框架来实现ListView中加载图片了,至于ViewPager还是别的,原理其实都是一样的 一.ListView 1.布局文件 list_layout.xml & ...
- 在Tabbed Activity(ViewPager)中切换Fragment
我用Android Studio的向导新建了一个Tabbed Activity,里面是ViewPager样式的,有三个tabs.如下: 但是我尝试在第一个tab中设置一个按钮,打开其他tab的时候,却 ...
- 关于fragment+viewpager的优化
上次写了一个问答项目,用的fragment+viewpager架构,后来发现,划了几次之后,再划回来,会重新加载布局,重新获取数据,这样整个程序和卡,并且占用太多的网络资源. 当时的解决办法是,自己重 ...
- 踩石行动:ViewPager无限轮播的坑
2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...
- Android ViewPager打造3D画廊
本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This cl ...
- Android中Fragment和ViewPager那点事儿(仿微信APP)
在之前的博文<Android中使用ViewPager实现屏幕页面切换和引导页效果实现>和<Android中Fragment的两种创建方式>以及<Android中Fragm ...
随机推荐
- window安装nginx,并解决前端跨域问题
window 安装 nginx 流程 第一步:下载nginx http://nginx.org/en/download.html 第二步:下载完成后,解压到指定目录文件,启动nginx 进入nginx ...
- Python 错误:TypeError: range() takes no keyword arguments
问题描述: for循环时使用range()出错: for page in range(start=1, stop=8 + 1,step=1): print(page) 结果报错TypeError: r ...
- centos7 系统运行中做raid磁盘阵列
插入磁盘 lsblk查看磁盘总体情况 对sdb1等需要做的硬盘进行制作 fdisk /dev/sdb 开始 n 创建 p 给资源回车 重选代码 t 确认磁盘阵列代码 fd 保存w 首先安装工具 mda ...
- mysql语句优化总结
Sql语句优化和索引 1.Innerjoin和左连接,右连接,子查询 A. inner join内连接也叫等值连接是,left/rightjoin是外连接. SELECT A.id,A.nam ...
- 负数位运算的右移操作-C语言基础
这一篇探讨的是"负数位运算的右移操作",涉及到数据的源码.反码.补码的转换操作.属于C语言基础篇. 先看例子 #include <stdio.h> int main(v ...
- TypeError: list indices must be integers or slices, not str解决方法
print (response.json()['data']['patientId'])TypeError: list indices must be integers or slices, not ...
- mysql-单行处理函数
1 单行处理函数 lower() 对于输出转换成小写 upper()对于输出转换成大写 substr()取子字符串 下标从1开始 length() 去长度 concat()将字符串进行拼接 例:将首字 ...
- 微信小程序中如何设置跳转页面
修改project.config.json内容 "cloudfunctionRoot":"cloud", //配置云开发的路径 更改app.js文件内容 App ...
- 用VUE框架开发的准备
使用VUE框架编写项目的准备工作 防止我几天不打代码,忘记怎么打了 下载小乌龟拉取码云项目文件,用于码云仓库代码提交与拉取(可以不安装) 小乌龟要设置你的码云账号 密码 在控制面版 中 凭证里可以修改 ...
- 01-第一个Spring程序
1.导包 所有和spring有关的包(有mybatis包的忽略),后期会使用maven引入 2. 引入spring的配置文件 可命名为applicationContext-service.xml或sp ...