Android之自定义ViewPager实现图片的无线轮播
PS:以前也写过关于图片轮播这一块的博客.不过写的很烂,并且很多情况没有考虑到(没有支持无线轮播,和手势点击事件).因此这里写一篇补上.也是当时太年轻了.
注:图片请放大后再看.否则看不清楚.
学习内容:
1.自定义ViewPager
2.图片无限轮播由于ViewPager的预加载机制所导致的问题.
以前也写过关于图片轮播的相关博客,不过总体写的非常的烂,并且不能够无线轮播,而且也无法对手势事件进行相关的处理,因此在这里补上,也是属于自定义View的一篇内容吧.并且通过这个过程发现了无限轮播由于ViewPager的预加载机制所导致的问题.也正赶上项目要上一个新的版本,发现了这个bug.我的同事想到了一个比较不错的方案解决了这个问题.总体还是很不错的.因此在这里记录一下.
总体实现无限轮播的思想,其实和网上大部分的思路都是相同的,设置一个Integer的最大值的一半,然后根据position和图片的数量去计算,来实现向左向右无限滑动这个功能.总体不是特别的难.自定义ViewPager之后,把相关的图片和跟随图片滑动时的小圆点传递到ViewPager当中,然后设置相关的滑动监听,Adapter就可以完美的实现图片的无限轮播了.
i.初始化轮播.
初始化轮播需要线程和Handler配合来完成,设置一个无线循环的线程,让这个线程按照一定的周期一直向Activity发送Message,Handler在接收到消息后会不断的对消息进行处理.改变ViewPager当前显示的CurrentItem就可以实现无限轮播了.
/**
* 初始化轮播的线程
*/
public void initLoop() { new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
if (!stopLoopTag) {
Message message = Message.obtain();
message.what = 10;
message.arg1 = getCurrentItem() + 1;
mHandler.sendMessage(message);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
} /**
* 处理轮播的Handler
*/
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 10 && !stopLoopTag) {
setCurrentItem(msg.arg1);
} else {
mHandler.removeMessages(10);
}
return true;
}
});
ii.图片轮播时小圆点随之变动
有了一个子线程和一个Handler就可以实现无线循环的一个过程,我们知道一般图片轮播都需要伴随小圆点的移动,小圆点一般是直接布局到ViewPager当中的,因为需要给用户一种更好的体验性,因此在图片轮播的同时伴随着小圆点也随之变动.那么小圆点如何在ViewPager的Item改变的时候也随之变动呢?这就需要addOnPageChangeListener()来实现了.
private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override
public void onPageSelected(int position) { /**
* 改变小圆点的状态
* 三种情况:
* 1.在初始位置
* 2.向左滑动
* 3.向右滑动
* 不要放在上一个方法中,会有明显的延迟现象出现
* lastPointPosition 上一个点的位置,初始化为0
* lastPointIndex 上一个点在集合中的位置
* currentPointIndex 当前的点在集合中的位置
* */
if (lastPointPosition == 0) { currentPointIndex = 0;
lastPointIndex = 0; } else if (lastPointPosition < position) { if (currentPointIndex == getImages().size() - 1) {
currentPointIndex = 0;
} else {
currentPointIndex += 1;
} } else if (lastPointPosition > position) { if (currentPointIndex == 0) {
currentPointIndex = getImages().size() - 1;
} else {
currentPointIndex -= 1;
} } dots.get(lastPointIndex).setImageResource(R.drawable.dot_normal);
dots.get(currentPointIndex).setImageResource(R.drawable.dot_focus);
lastPointPosition = position;
lastPointIndex = currentPointIndex;
} @Override
public void onPageScrollStateChanged(int state) { }
};
这里我们通过addOnPageChangeListener()绑定Page的改变监听来改变小圆点随着Page改变的同时随之改变.图片的改变需要在适配器里去设置,适配器我留到最后说,因为其中有很多的细节.这样有了无限循环,原点移动,那么就需要说一下当我们手指停留在ViewPager的时候,如何使ViewPager停止播放.因为涉及到了手势事件,因此就要重写相关的方法.
iii.重写手势事件
/**
* 手势事件的重写
*/
@Override
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN:
/**
* 按下的时候停止轮播
* */
stopLoop();
xDown = (int) event.getX();
yDown = (int) event.getY();
break; case MotionEvent.ACTION_MOVE:
/**
* 这里不用做任何处理,移动的时候ViewPager内的图片会自动滑动
* */
break; case MotionEvent.ACTION_UP: /**
* 记录按下时间
* */
if (timeThread == null) {
touchTime = new Date().getTime();
timeTag = true;
timeThread = new TimeThread();
timeThread.start();
} else {
touchTime = new Date().getTime();
} /**
* 判断是否是点击事件
* */
int xUp = (int) event.getX();
int yUp = (int) event.getY();
if (Math.abs(xDown - xUp) < 20 && Math.abs(yDown - yUp) < 20) { if (onImageItemClickListener != null) {
onImageItemClickListener.onItemClick(currentPointIndex);
}
}
break;
}
return super.onTouchEvent(event);
}
手势事件的重写就非常的简单了,当点击的时候停止轮播,移动的时候我们不需要做任何的处理,在手指离开之后,我们需要计算离开的时间,离开的时间超过两秒,就再次开启轮播.关闭轮播只需要停止最开始的那个子线程,让标记位StopLoopTag为true,再将消息队列中的消息移除队列.这样Handler也接收不到消息了.这样就停止了轮播的效果.计算手指按下和离开的时间也需要一个子线程去处理.
/**
* 时间线程,用于记录手指离开点击ViewPager的时间
* 如果离开的时间 >= 2000毫秒,那么ViewPager继续轮播
*/
class TimeThread extends Thread { @Override
public void run() {
while (timeTag) {
long currentTime = new Date().getTime();
if (currentTime - touchTime >= 2000) {
openLoop();
timeTag = false;
timeThread = null;
}
}
}
}
iv.PagerAdapter.
PagerAdapter也是最蛋疼的一块.instantiateItem()方法里的那一大堆代码是关键.很多人都用这样的代码来实现图片的加载过程.
@Override
public Object instantiateItem(ViewGroup container, int position) { position %= images.size();
if (position < 0) {
position = position + images.size();
}
ImageView imageView = images.get(initPosition);
ViewParent parent = imageView.getParent();
if (parent != null) {
ViewGroup viewGroup = (ViewGroup) parent;
viewGroup.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
其实这样写是有很大的问题的,因为这里没有考虑到ViewPager的预加载机制的问题,这也是我们项目出现的一个bug,我们是从服务器上获取相关的图片数据,然后保存在集合当中,如果我们在将图片加载到ViewPager中的时候,仅调用image.get(position),position为上面代码计算出的position,这样的话实际上会导致图片错位.虽然显示的是5张图片,但是实际上他们在ViewPager中显示的顺序是不对的.比如说我们在后台定义了这样的顺序, 1,2,3,4,5的顺序发送给我们,但是如果我们按照上面的方法从集合中拿数据的时候,ViewPager显示的不是1,2,3,4,5这样的顺序,这也是我们在项目中发现的问题.因为在点击图片的时候,是需要走不同的链接的,也正是这个原因我们发现了这个bug.因此这里做了很多的处理.
public class PictureAdapter extends PagerAdapter { private List<ImageView> images; /**
* initPosition -1为初始化的位置,后面是当前的图片索引位置
* topPosition 记录上一次初始化的索引位,用于计算上次的position和本次position的偏移量
*
* */
private int initPosition = -1;
private int topPosition = -1; public PictureAdapter(List<ImageView> images) {
this.images = images;
} @Override
public int getCount() {
return Integer.MAX_VALUE;
} @Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
} @Override
public void destroyItem(ViewGroup container, int position, Object object) { } /**
* 实例化Item
*/
@Override
public Object instantiateItem(ViewGroup container, int position) { if(images.size() <=1){
ImageView imageView = images.get(topPosition);
container.addView(imageView);
return imageView;
}else{
/**
* 初始化状态
* 向左滑动
* 向右滑动
* 由于ViewPager有预加载机制,默认加载一页,因此在第一次初始化的时候,会调用三次这个方法.
* (第一次: position=1073741823 第二次: position=1073741822 第三次: position=1073741824)
*
* 而后续,这个方法仅被执行一次,并且执行的是预加载下一页的请求.
* */
Log.e("TAG","position="+position);
if (initPosition == -1) {
/**
* 初始化状态
* topPosition 记录第一次初始化的索引位.用于后续作比较判断下次是向右滑动还是向左滑动
* initPosition 初始化图片集合的索引值
* */
topPosition = position;
initPosition = 0;
} else if (topPosition < position) {
/**
* 向左滑动
* 得出偏移量后比较是否超过图片集合的大小
* */
int value = position - topPosition;
initPosition += value;
if (initPosition == images.size()) {
/**
* 滑动到了最后一页
* */
initPosition = 0;
} else if (initPosition > images.size()) {
/**
* 如果超出了图片集合的大小,则 initPosition = 超过的数值
* */
initPosition = (initPosition - images.size());
}
topPosition = position;
} else if (topPosition > position) {
int value = topPosition - position;
initPosition -= value;
if (initPosition == -1) {
/**
* 滑动到了第一页
* */
initPosition = images.size() - 1;
} else if (initPosition < -1) {
/**
* 当计算后的值小于了集合大小,则用集合大小减去小于的这部分
* */
initPosition = (images.size() - (Math.abs(initPosition)));
}
topPosition = position;
}
Log.e("TAG","topPosition="+topPosition);
Log.e("TAG","initPosition="+initPosition);
/**
* 只用这句话应该会出现问题
* */
// position %= images.size();
// if (position < 0) {
// position = position + images.size();
// }
ImageView imageView = images.get(initPosition);
ViewParent parent = imageView.getParent();
if (parent != null) {
ViewGroup viewGroup = (ViewGroup) parent;
viewGroup.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
}
}
这就是我们定义的PagerAdapter.里面做了很多的逻辑处理.一张图解释一下其中的原理.
这张图说明了一种的道理,上面代码不难发现.会出现 (initPosition > images.size())和initPosition < -1这两种情况.这种原因的导致就是因为position会由于这种预加载机制出现数值跳跃问题.这里大家可以去根据Log信息结合我说的原理,好好的研究一下.相信大家会研究明白的.
最后就是点击事件了.对Activity暴露接口,让Activity去实现就可以了.
/**
* 对Activity暴露接口
*/
private OnImageItemClickListener onImageItemClickListener; public interface OnImageItemClickListener {
void onItemClick(int itemPosition);
} public void setOnImageItemClickListener(OnImageItemClickListener onImageItemClickListener) {
this.onImageItemClickListener = onImageItemClickListener;
}
剩下的就是MainActivity了.这里就不粘贴代码了.里面的内容比较的简单.这样就实现了图片的无线轮播,并且支持点击停止,松开继续播放的功能,还有的图片点击事件.最后放一个源代码:
源代码地址:http://pan.baidu.com/s/1dFqig17
Android之自定义ViewPager实现图片的无线轮播的更多相关文章
- Android RatingBar自定义替换系统图片
1.自定义替换提醒☆图片,准备两个图片添加到系统中去:如下: 在drewable下定义一个图片资源ratingbar_drawable.xml 1 2 3 4 5 6 7 8 9 10 ...
- viewpager无线轮播获取网络图片
首先创建小圆点 小圆点的两个属性一共两个属性另一个跟这个一个样,只不过颜色能让我们区分的 <?xml version="1.0" encoding="utf-8&q ...
- Android progressBar 自定义圆形旋转图片
项目需要中需要更换progressbar的旋转背景,在网上找了几种办法,但是都有各自的问题 于是结合网上所讲,研究了一下终于ok了: 一 首相在drawable文件夹中建立如下旋转动画文件 <? ...
- android中自定义checkbox的图片和大小
其实很简单,分三步: 1.在drawable中创建文件checkbox_selector.xml: <?xml version="1.0" encoding="ut ...
- 自定义ViewPager+RadioGroup联动效果的实现
package com.loaderman.myviewpager; import android.os.Bundle; import android.support.v7.app.AppCompat ...
- [转]Android中自定义checkbox样式
android中自定义checkbox的图片和大小 其实很简单,分三步: 1.在drawable中创建文件checkbox_selector.xml: <?xml version=" ...
- Android实现图片轮显效果——自定义ViewPager控件
一.问题概述 使用ViewPager控件实现可横向翻页.水平切换图片等效果,但ViewPager需要手动滑动才能切换页面,图片轮显效果的效果本质上就是在ViewPager控件的基础上让它能自动的进行切 ...
- [Android] 对自定义图片浏览器经常内存溢出的一些优化
首先关于异步加载图片可以参见 夏安明 的博客:http://blog.csdn.net/xiaanming/article/details/9825113 这篇文章最近有了新的更改,大概看了一下,内容 ...
- 三行代码接入,社交软件打字时底下弹出的表情布局,自定义ViewPager+页面点标+各种功能的android小框架。
(转载请声明出处:http://www.cnblogs.com/linguanh/) 前言: 接上次分享的 ListView 动态加载类,入口:http://www.cnblogs.com/lingu ...
随机推荐
- 更加精确的定时器:dispatch_source_t
在使用定时器时,我们经常使用NSTimer,但是由于NSTimer会受RunLoop影响,当RunLoop处理的任务很多时,就会导致NSTimer的精度降低,所以在一些对定时器精度要求很高的情况下,应 ...
- 浅谈Js原型的理解
一.js中的原型毫无疑问一个难点,学习如果不深入很容易就晕了! 在参考了多方面的资料后,发现解释都太过专业,对于很多还没有接触过面向对象 语言的小白来说,有理解不了里面的专有名词!如果你没 ...
- $Host.Runspace.ThreadOptions = “ReuseThread”有神马用?
$Host.Runspace.ThreadOptions = “ReuseThread” 在很多PowerShell的脚本中你都会看到这个语句被用来开头,那它的作用是什么呢? 答:这个设置可以提高对内 ...
- SQL Server数据库备份的镜像
SQL Server数据库备份的镜像 一个完整备份可以分开镜像 USE master GO BACKUP DATABASE [testdatabase] TO DISK = N'C:\testdata ...
- 基于AngularJS的企业软件前端架构[转载]
这篇是我参加QCon北京2014的演讲内容: 提纲: 企业应用在软件行业中占有很大的比重,而这类软件多数现在也都采用B/S的模式开发,在这个日新月异的时代,它们的前端开发技术找到了什么改进点呢? B/ ...
- 玩转Windows服务系列——创建Windows服务
创建Windows服务的项目 新建项目->C++语言->ATL->ATL项目->服务(EXE) 这样就创建了一个Windows服务项目. 生成的解决方案包含两个项目:Servi ...
- 人生苦短,我用python——当我在玩python的时候我玩些什么
文章背景 家里的第一台电脑是在2006年夏天买的,10年上大学之后基本上就没人用,过没两年就当二手卖给一个熟人. 弟弟小我10岁,今年刚上初一.他在我毕业前半年就整天用妈妈的手机发短信给我,问我什么时 ...
- Sharing A Powerful Tool For Calculate Code Lines
最近正好需要统计下某项目代码行数,然后就找代码行数统计工具.以前找到过一个正则表达式,但是只有在VS2010下有用,VS2012和VS2013下的统计就不好使了. 接着搜索了一下代码行数统计绿色工具免 ...
- C语言 · 出现次数最多的数
问题描述 编写一个程序,读入一组整数,这组整数是按照从小到大的顺序排列的,它们的个数N也是由用户输入的,最多不会超过20.然后程序将对这个数组进行统计,把出现次数最多的那个数组元素值打印出来.如果有两 ...
- Atitit View事件分发机制
1. Atitit View事件分发机制 1. Atitit View事件分发机制1 1.1. 三个关键方法 dispatchTouchEvent onInterceptTouchEvent onTo ...