Android实现一个丝滑的自动轮播控件实例代码
作者:developerHaoz 发布时间:2022-01-24 03:08:19
前言
现在很多的 App 都有自动轮播的 banner 界面,用于展示广告图片或者显示当前比较热门的一些活动,除了具备比较酷炫的效果之外,通过轮播的方式来减少对界面的占用,也是很赞的一个设计点。本文主要是总结自动轮播控件的实现过程,以及对这类控件的一些优化的技巧。
一、如何实现
在开始进行我们的代码编程之前,我们先要思考一下,在 Google 提供的官方 Api 里面,有没有类似的控件实现了相似的功能,毕竟官方的控件大都经过了时间的考验,无论是稳定性还是性能方面都是非常不错的,如果我们能够基于官方的控件进行相应的改造,控件的稳定性也会有相对的保障。
在比较常见的主流控件里面,其实 ViewPager 和 RecyclerView 已经实现了类似的功能,尤其是 ViewPager,可以说是已经实现了我们这个控件的大部分功能,所以如果我们基于 ViewPager 来进行改造的话,也能让我们的轮播控件更加稳定。
那 ViewPager 跟我们需要的自动轮播控件有多少差距呢,主要有两个:
不支持自动播放
无法从最后一张滑动到第一张
所以我们主要是针对这两部分进行相应的改造,从而实现我们自己的自动轮播控件。
1.1 实现自动轮播功能
要想实现自动轮播功能,我们最先想到的应该是通过 Timer 或者 ScheduledExecutorService 来实现计时器的功能,然后让 ViewPager 通过 serCurrentItem(int position) 方法,将当前的 Item 设置为下一个 position 的数据,但是如果通过定时器来实现的话,会有一个问题,那就是我们在需要让 banner 进行停止播放的时候就比较麻烦,所以通过 Handler 用 sendMessage 的形式,进行事件的发送实现 ViewPager 的自动轮播,以及某些场景的停止是比较合理的。
我们来看下代码:
private static class AutoScrollHandler extends Handler {
private WeakReference<AutoScrollViewPager> mBannerRef;
private static final int MSG_CHANGE_SELECTION = 1;
AutoScrollHandler(AutoScrollViewPager autoScrollViewPager) {
mBannerRef = new WeakReference<>(autoScrollViewPager);
}
private void start() {
removeMessages(MSG_CHANGE_SELECTION);
sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
}
private void stop() {
removeMessages(MSG_CHANGE_SELECTION);
}
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_CHANGE_SELECTION) {
if (mBannerRef == null || mBannerRef.get() == null) {
return;
}
AutoScrollViewPager banner = mBannerRef.get();
if (banner.mSelectedIndex == Integer.MAX_VALUE) {
int rightPos = banner.mSelectedIndex % banner.mBannerList.size();
banner.setCurrentItem(banner.getInitPosition() + rightPos + 1, true);
} else {
if (!hasMessages(MSG_CHANGE_SELECTION)) {
banner.setCurrentItem(banner.mSelectedIndex + 1, true);
sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
}
}
}
}
}
可以看到,我们先传入外部的 ViewPager,然后通过弱引用的形式防止内存泄露,通过在 handlerMessage() 方法里面,调用 setCurrentItem() 方法,将当前 ViewPager 的 Item 设置为对应的 position + 1 的数据,所以我们只要在外部调用 Handler 的 sendMessage() 方法,就能使 ViewPager 进行自动的无限轮播。
1.2 让 ViewPager 从最后一张滑动到第一张
我们知道,ViewPager 是无法从最后一页滑动到第一页的,但我们可以换一个思路,如果我们在 ViewPager 的 Adapter 里面,通过 getCount() 方法将 ViewPager 的大小设置为无限大,然后通过取余的方式来保证滑动的页面一直对应数据源的那几个数据,这样便能让 ViewPager 实现从最后一张滑动到第一张的效果。
public int getCount() {
if (mBannerList == null) {
return 0;
}
if (mBannerList.size() == 1) {
return 1;
} else {
return Integer.MAX_VALUE;
}
}
public Object instantiateItem(ViewGroup container, final int position) {
if (mBannerList != null && mBannerList.size() > 0) {
View imageView = null;
Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url); // 通过取余的方式
imageView = new SimpleDraweeView(mContext);
((SimpleDraweeView) imageView).setImageURI(uri);
container.addView(imageView);
return imageView;
}
return null;
}
二、如何进行优化
在上面我们只是简单的实现了 ViewPager 的自动轮播功能,但其实还有很多的细节需要我们进行优化,例如:我们是通过将 ViewPager 的大小设置为无限大的方式,来实现从最后一张滑动到第一张的,但这时候如果不进行缓存的话,我们在 Adapter 的 instantiateItem(ViewGroup container, final int position) 方法里面,便需要返回很多新 new 出来的 View,这样会造成不必要的内存浪费,只有对这些细节进行优化,才能让我们的控件更加的好用,稳定性和性能方面也会更加优异。
2.1 通过缓存减少内存浪费
为了让 ViewPager 能实现无线轮播的功能,我们是使用了通过将 getCount() 的大小设置为无限大的方式来实现的,但这会产生一个问题,这样会使我们在 Adapter 的 instantiateItem() 方法中返回很多新 new 出来的 View,而造成不必要的内存浪费。
所以我们可以通过一个 List 作为缓存池,在 Adapter 中的 destroyItem() 方法中将废弃的 object 存到缓存池中,重复利用,这样便能避免内存浪费。
private final ArrayList<View> mViewCaches = new ArrayList<>();
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
ImageView imageView = (ImageView) object;
container.removeView(imageView);
mViewCaches.add(imageView);
}
然后在 Adapter 的 instantiateItem() 方法中,从 List 中取出已经被缓存的 View,进行重复利用
public Object instantiateItem(ViewGroup container, final int position) {
if (mBannerList != null && mBannerList.size() > 0) {
View imageView = null;
Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url);
if (mViewCaches.isEmpty()) {
imageView = new SimpleDraweeView(GlobalContext.getContext());
} else {
// 当缓存集合有数据时,进行复用
imageView = (ImageView) mViewCaches.remove(0);
}
}
2.2 适当的停止自动轮播
当我们触摸 Banner 或者离开当前展示 Banner 的页面时,如果 banner 还在不停的进行无线轮播的话,会造成没必要的性能损失,所以我们需要在触摸 Banner 以及当前的 Activity 为不可见状态的时候,停止 Banner 的轮播,从而提升性能。
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_OUTSIDE) {
startAutoPlay();
} else if (action == MotionEvent.ACTION_DOWN) {
stopAutoPlay();
}
return super.dispatchTouchEvent(ev);
}
2.3 改变 ViewPager 切换速度
原生的 ViewPager 在进行自动轮播的时候,切换速度是特别快的,会给人一种很突兀的感觉,而且 ViewPager 也没有提供接口给我们对 ViewPager 进行切换速度的设置,所以我们需要通过反射的方式,使用 Scroller 来进行切换速度的设置,从而让我们的 Banner 更加的丝滑。
public AutoScrollViewPager(Context context) {
this(context, null);
initViewPagerScroll();
}
private void initViewPagerScroll() {
try {
Field mField = ViewPager.class.getDeclaredField("mScroller");
mField.setAccessible(true);
BannerScroller scroller = new BannerScroller(getContext());
mField.set(this, scroller);
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
}
public class BannerScroller extends Scroller {
private static final int BANNER_DURATION = 1000;
private int mDuration = BANNER_DURATION;
public BannerScroller(Context context) {
super(context);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
至此,我们的自动轮播控件,无论是性能上还是稳定性上都已经很不错了。
来源:https://www.jianshu.com/p/ef6ff245ed3e


猜你喜欢
- break和continue的说明break 循环结构,一旦执行,就结束(或跳出)当前循环结构,此关键字的后面,不能
- Android 切圆图效果图如下:MyView 类public class MyView extends View {Bitmap bmp;
- 本文实例讲述了WinForm生成验证码图片的方法。分享给大家供大家参考,具体如下:1、创建ValidCode类:public class V
- C#可以直接引用C++的DLL和转换JAVA写好的程序。最近由于工作原因接触这方面比较多,根据实际需求,我们通过一个具体例子把一个JAVA方
- Mybatis-plus官网地址:https://baomidou.com/配置mysql在配置文件连接mysqlspring.dataso
- 1.内部类概念及分类将一个类定义在另一个类的内部或者接口内部或者方法体内部,这个类就被称为内部类,我们不妨将内部类所在的类称为外围类,除了定
- C/C++的数据类型:一,整型Turbo C: [signed] int 2Byte//有符号数,-32768~32
- Android自定义View实现等级滑动条的实例实现效果图:思路: 首先绘制直线,然后等分直线绘制点; 绘制点的时候把X值存到集
- 什么是Run Dashboard当springcloud的服务有多个时,管理多个服务的启动使用run会不好管理,这样我们就可以使用Run D
- 可重入锁,从字面来理解,就是可以重复进入的锁。可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但
- 前言ps命令的作用是显示进程信息的。|符号,是个管道符号,表示左右两边两个命令同时执行。grep命令是查找(Global Regular E
- 相关函数:longjmp, siglongjmp, setjmp表头文件:#include <setjmp.h>函数定义:int
- PowerMockito 测试静态方法假如有下面一个类DemoStatic,它里面定义了各种静态方法,这些静态方法可能是一些Utilitie
- Spring的 * 缓存Spring * 缓存是为了解决对象间的循环依赖问题。A依赖B,B依赖A,这就是一个简单的循环依赖。我们来先看看 * 缓存
- 之前有简单介绍过java多线程的使用,已经Thread类和Runnable类,为了更好地理解多线程,本文就Thread进行详细的分析。sta
- springboot static调用service为null@PostConstruct注解好多人以为是Spring提供的。其实是Java
- 前言最近遇到一个Jvm old过高的案例,现象是一个站点的jvm old区过高,分析原因是,原来的设计方案有问题,给前端返回的数据里面包含了
- 方法有4种:使用 String 类的 valueOf() 方法使用字符串连接使用 Character 类的 toString() 方法使用字
- 本文实例为大家分享了Java操作MongoDB模糊查询和分页查询,供大家参考,具体内容如下模糊查询条件:1、完全匹配Pattern patt
- 使用C#在不借助第三方插件的情况下将Excel中的数据转换成DataSet/// <summary>