概述:
之前有个需求是写一个公告,需要无限轮询效果,第一时间想到的是用viewpager实现。网上一看,几乎都是用viewpager实现的。于是我也手动实现了一下,发现其实效果也没那么好。实现无限轮询,网上一般有两种做法,1.给予viewpager的页数一个很大的值,比如Integer.MAX_VALUE。这样做的话不知道为什么总是不大流畅,有时还出现空白页,再者切换到下一页是可以的,但是切换回上一页有可能回到第一页再也不能切换了,解决的方法是一开始跳到中间去。 2.是缓存几页,等切换到最后一页时,下一页是缓存的第一页,切换到下一页后马上跳到第一页,实现一个循环。由于很快,看不出变化,这样的做法是去掉了切换页的效果。 总之也是蛮多问题,勉强实现了效果,在我看来不是一个比较好的解决的办法。在我思考了好一阵,决定写这个控件,也当作练习自定义控件吧。
基本需求:
1.实现自动无限轮询切换。
2.支持滑动切换。
3.支持滑动改变切换方向。
4.为了方便拓展,支持存放任何view。
5.支持点击事件监听,以便知道点击了那个控件。
最终效果:
实现原理:
为了实现能向上或者向下轮询,可以采用咬尾蛇的方式,就是view之间联系起来,最开始与最后的view也关联起来,形成一个真正的循环的圈子。如下图显示:
为了好处理,将其封装在一个结构体里面,如我写的这样:
public class BannerViewdata {
private int index;
private BannerViewdata previewdata;
private BannerViewdata nextviewdata;
private View view;
private Boolean isfirst = false;
private Boolean islast = false;}
当在滑动的时候,由于只会看到两个view,通过判断到底是执行滑动上一页还是执行滑动下一页,然后在这个咬尾蛇圈里面获取对应的三个view就可以了。由于它是一个循环圈,所以真正实现了只需要缓存几个view就能无限循环下去了。
代码实现逻辑:
1.封装BannerViewdata,为了将各个要轮询显示的view关联起来。
2.自定义BannerViewGroup,继承viewgroup,基本逻辑是:
2.1将所有BannerViewdata的view添加进去。
2.2 显示的话总共有三个view,前一个,中间一个,下一个,通过leftdividerxposition 与rightdividerxposition位 置分隔开。
2.3根据滑动的leftdividerxposition与位置rightdividerxposition,调用onlayout方法刷新显示的三个view的位置。
2.4完成了前一页或者下一页移动后,中间页数据也相应的向后或者向前移动,这样完成一次切换。
代码:
BannerViewdata:
/**
* @author huangwei
* 2016年9月1日下午5:09:16
*
*/
public class BannerViewdata {
private int index;
private BannerViewdata previewdata;
private BannerViewdata nextviewdata;
private View view;
private Boolean isfirst = false;
private Boolean islast = false;
/**
* @param context
* @param index 下标
* @param previewdata 前一个数据
* @param nextviewdata 后一个数据
* @param view view,不运行为空
* @param isfirst 用于标识是否是第一个view
* @param islast 用于标识是否是最后一个view
*/
public BannerViewdata(Context context, int index, BannerViewdata previewdata, BannerViewdata nextviewdata,
View view, Boolean isfirst, Boolean islast) {
this.index = index;
this.previewdata = previewdata;
this.nextviewdata = nextviewdata;
this.isfirst = isfirst;
this.islast = islast;
setView(view, context);
if (view == null) {
throw new NullPointerException();
}
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public BannerViewdata getPreviewdata() {
return previewdata;
}
public void setPreviewdata(BannerViewdata previewdata) {
this.previewdata = previewdata;
}
public BannerViewdata getNextviewdata() {
return nextviewdata;
}
public void setNextviewdata(BannerViewdata nextviewdata) {
this.nextviewdata = nextviewdata;
}
public View getView() {
return view;
}
public void setView(View view, Context context) {
RelativeLayout relativeLayout = new RelativeLayout(context);
relativeLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
relativeLayout.addView(view);
this.view = relativeLayout;
}
public Boolean istheFirst() {
return isfirst;
}
public Boolean istheLast() {
return islast;
}
public int getLength() {
BannerViewdata first = findtheFirstViewdata();
if (first == null)
return 0;
BannerViewdata next = first.getNextviewdata();
if (next == null) {
return 1;
}
if (next.islast) {
return 2;
}
int length = 1;
while (next != null) {
length++;
next = next.getNextviewdata();
if (next.islast) {
return ++length;
}
}
return length;
}
/**
* @return 返回最开始的 BannerViewdata,也就是下标为0的,如果没有那么就返回null
*/
public BannerViewdata findtheFirstViewdata() {
if (istheFirst()) {// 当前为0,那么就是最开始的值了
return this;
}
BannerViewdata bannerViewdata = previewdata;
while (bannerViewdata != null && !bannerViewdata.istheFirst()) {
bannerViewdata = bannerViewdata.getPreviewdata();
}
return bannerViewdata;
}
}
/**
* @author huangwei 2016年8月31日下午5:24:02
*
*/
public class BannerViewGroup extends ViewGroup {
private static final int PAGE_UP = 0X04, PAGE_DOWN = 0X05;
private int movestate = PAGE_DOWN;
private int automsiwtchtime = 3000;// 自动切换时间
private int leftdividerxposition, rightdividerxposition;
private int x0, actiondownx0;
private BannerViewdata mBannerViewdata;
private BannerViewdata prepagedata;
private BannerViewdata nextpagedata;
private Boolean onAimation = false;
private Boolean startupautomswitch = true;// 启动自动切换
private Handler handler;
private BannerClickListenr bannerClickListenr;
private long downtime;// action down时的时间,用于判断是不是点击事件
public BannerViewGroup(Context context) {
super(context);
}
public BannerViewGroup(Context context, AttributeSet attrs) {
super(context, attrs, 0);
init();
}
public BannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@SuppressLint("NewApi")
public BannerViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private TimerTask timetask = new TimerTask() {
@Override
public void run() {
if (movestate == PAGE_DOWN) {
handler.sendEmptyMessage(PAGE_DOWN);
} else {
handler.sendEmptyMessage(PAGE_UP);
}
}
};
private void init() {
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PAGE_DOWN:
if (!onAimation) {
doPagedownAnimation();
}
break;
case PAGE_UP:
if (!onAimation) {
doPageUpAnimation();
}
break;
default:
break;
}
}
};
}
public void startupautomswitch() {
startupautomswitch = true;
Timer timer = new Timer();
if (mBannerViewdata.getLength() > 1) {
timer.schedule(timetask, 1000, automsiwtchtime);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int cWidth = 0;
int cHeight = 0;
cWidth = getMeasuredWidth();
cHeight = getMeasuredHeight();
int childcount = getChildCount();
// 隐藏所有可见view,这是为了清除可能出现重叠的情况
for (int i = 0; i < childcount; i++) {
View childview = getChildAt(i);
childview.layout(-cWidth, 0, 0, 0);
}
if (prepagedata != null) {
View childView1 = prepagedata.getView();
if (childView1 != null) {
int cl1 = 0, ct1 = 0, cr1 = 0, cb1 = 0;
cl1 = leftdividerxposition - cWidth;
cr1 = leftdividerxposition;
cb1 = cHeight + ct1;
childView1.layout(cl1, ct1, cr1, cb1);
}
}
if (mBannerViewdata != null) {
View childView2 = mBannerViewdata.getView();
if (childView2 != null) {
int cl1 = 0, ct1 = 0, cr1 = 0, cb1 = 0;
cl1 = leftdividerxposition;
cr1 = rightdividerxposition;
cb1 = cHeight + ct1;
childView2.layout(cl1, ct1, cr1, cb1);
}
}
if (nextpagedata != null) {
View childView3 = nextpagedata.getView();
if (childView3 != null) {
int cl2 = rightdividerxposition, ct2 = 0, cr2 = rightdividerxposition, cb2 = 0;
cr2 = cl2 + cWidth;
cb2 = cHeight + ct2;
childView3.layout(cl2, ct2, cr2, cb2);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
*/
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
// 计算出所有的childView的宽和高
measureChildren(MeasureSpec.makeMeasureSpec(sizeWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(sizeHeight, MeasureSpec.AT_MOST));
leftdividerxposition = 0;
rightdividerxposition = sizeWidth + leftdividerxposition;
/**
* 直接设置为父容器计算的值
*/
setMeasuredDimension(sizeWidth, sizeHeight);
}
/**
* 添加数据
*
* @param bannerViewdata
*/
public void setBannerViewData(BannerViewdata bannerViewdata) {
if (!checkBannersecurity(bannerViewdata)) {
return;
}
mBannerViewdata = bannerViewdata;
mBannerViewdata = mBannerViewdata.findtheFirstViewdata();
if (!checkBannersecurity(bannerViewdata)) {
return;
}
prepagedata = mBannerViewdata.getPreviewdata();
nextpagedata = mBannerViewdata.getNextviewdata();
addview();
if (startupautomswitch) {
startupautomswitch();
}
}
private void addview() {
BannerViewdata viewdata = mBannerViewdata;
while (checkBannersecurity(viewdata)) {
addView(viewdata.getView());
viewdata = viewdata.getNextviewdata();
if (checkBannersecurity(viewdata) && viewdata.istheLast()) {
addView(viewdata.getView());
return;
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 将事件拦截掉
return true;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
if (onAimation) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downtime = System.currentTimeMillis();
x0 = x;
actiondownx0 = x0;
break;
case MotionEvent.ACTION_MOVE:
int offsetx = x - x0;
x0 = x;
leftdividerxposition = leftdividerxposition + offsetx;
rightdividerxposition = leftdividerxposition + getWidth();
if (x < actiondownx0) {
movestate = PAGE_DOWN;
} else {
movestate = PAGE_UP;
}
onLayout(true, getLeft() + offsetx, getTop(), getRight() + offsetx, getBottom());
break;
case MotionEvent.ACTION_UP:
doclickListener(x);
Boolean hasmoved = Math.abs(x - actiondownx0) > 5;
if (hasmoved) {
if (x < actiondownx0) {
movestate = PAGE_DOWN;
doPagedownAnimation();
} else {
movestate = PAGE_UP;
doPageUpAnimation();
}
} else {
if (x > getWidth() / 2) {
movestate = PAGE_DOWN;
doPagedownAnimation();
} else {
movestate = PAGE_UP;
doPageUpAnimation();
}
}
break;
default:
break;
}
return true;
}
/**
* 点击事件监听
*
* @param x
*/
private void doclickListener(int x) {
if (isckickeven()) {
if (bannerClickListenr == null)
return;
if (x < leftdividerxposition) {
if (prepagedata != null) {
bannerClickListenr.onckick(prepagedata.getIndex(), prepagedata);
}
} else {
if (nextpagedata != null) {
bannerClickListenr.onckick(nextpagedata.getIndex(), nextpagedata);
}
}
}
}
/**
* 用于判断是否是执行了点击事件
*
* @return
*/
private Boolean isckickeven() {
long timeoffset = System.currentTimeMillis() - downtime;
downtime = 0;
return timeoffset > 30 && timeoffset < 100;
}
/**
* 执行上翻动画
*/
private void doPageUpAnimation() {
final ValueAnimator valueAnimator = getValueAnimator();
valueAnimator.setDuration(1000);
final int startposition = leftdividerxposition;
final int distance = getWidth() - startposition;
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
leftdividerxposition = (int) (startposition + distance * value);
rightdividerxposition = leftdividerxposition + getWidth();
if (leftdividerxposition >= getWidth()) {
valueAnimator.cancel();
mBannerViewdata = mBannerViewdata.getPreviewdata();
committchange();
onAimation = false;
return;
}
onLayout(true, 0, 0, 0, 0);
}
});
valueAnimator.start();
}
private void committchange() {
if (mBannerViewdata != null) {
prepagedata = mBannerViewdata.getPreviewdata();
nextpagedata = mBannerViewdata.getNextviewdata();
leftdividerxposition = 0;
rightdividerxposition = leftdividerxposition + getWidth();
}
}
/**
* 执行下翻动画
*/
private void doPagedownAnimation() {
final ValueAnimator valueAnimator = getValueAnimator();
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
rightdividerxposition = (int) (rightdividerxposition * (1 - value));
leftdividerxposition = rightdividerxposition - getWidth();
if (rightdividerxposition <= 0) {
valueAnimator.cancel();
mBannerViewdata = mBannerViewdata.getNextviewdata();
committchange();
onAimation = false;
return;
}
onLayout(true, 0, 0, 0, 0);
}
});
valueAnimator.start();
}
private ValueAnimator getValueAnimator() {
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(3000);
valueAnimator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
onAimation = true;
return valueAnimator;
}
private Boolean checkBannersecurity(BannerViewdata bannerViewdata) {
return bannerViewdata != null;
}
@Override
public void addView(View child) {
super.addView(child);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
timetask.cancel();
handler = null;
}
/**
* 设置点击事件监听
*
* @param bannerClickListenr
*/
public void setOnBannerClickListenr(BannerClickListenr bannerClickListenr) {
this.bannerClickListenr = bannerClickListenr;
}
public interface BannerClickListenr {
/**
* @param index
* 这个其实是点击的bannerViewdata的index
* @param bannerViewdata
* 这个其实是点击的bannerViewdata
*/
public void onckick(int index, BannerViewdata bannerViewdata);
}
}
代码下载地址:github