Android自定义Banner控件,完美实现无限轮询

概述:

        之前有个需求是写一个公告,需要无限轮询效果,第一时间想到的是用viewpager实现。网上一看,几乎都是用viewpager实现的。于是我也手动实现了一下,发现其实效果也没那么好。实现无限轮询,网上一般有两种做法,1.给予viewpager的页数一个很大的值,比如Integer.MAX_VALUE。这样做的话不知道为什么总是不大流畅,有时还出现空白页,再者切换到下一页是可以的,但是切换回上一页有可能回到第一页再也不能切换了,解决的方法是一开始跳到中间去。    2.是缓存几页,等切换到最后一页时,下一页是缓存的第一页,切换到下一页后马上跳到第一页,实现一个循环。由于很快,看不出变化,这样的做法是去掉了切换页的效果。 总之也是蛮多问题,勉强实现了效果,在我看来不是一个比较好的解决的办法。在我思考了好一阵,决定写这个控件,也当作练习自定义控件吧。


基本需求

                        1.实现自动无限轮询切换。

                        2.支持滑动切换。

                        3.支持滑动改变切换方向。

                        4.为了方便拓展,支持存放任何view。

                        5.支持点击事件监听,以便知道点击了那个控件。


最终效果:




实现原理:

        为了实现能向上或者向下轮询,可以采用咬尾蛇的方式,就是view之间联系起来,最开始与最后的view也关联起来,形成一个真正的循环的圈子。如下图显示:

Android自定义Banner控件,完美实现无限轮询_第1张图片



为了好处理,将其封装在一个结构体里面,如我写的这样:


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;
	}

}

BannerViewGroup:


/**
 * @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);
	}
}

注明:以上代码已经比较好的实现了需求的效果,唯一还需要改善的是,传入的view必须有三个才能形成一个循环,不够三个的情况没有做处理,需求使用的请自动改善。


转载必须注明出处:http://blog.csdn.net/u014614038/article/details/52410671

代码下载地址:github

                        

你可能感兴趣的:(android进阶,Android自定义控件)