Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義Banner控件,完美實現無限輪詢

Android自定義Banner控件,完美實現無限輪詢

編輯:關於Android編程

概述:

之前有個需求是寫一個公告,需要無限輪詢效果,第一時間想到的是用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;
	}

}

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必須有三個才能形成一個循環,不夠三個的情況沒有做處理,需求使用的請自動改善。

代碼下載地址:github

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved