Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android事件分發學習應用-圖片輪播實現

Android事件分發學習應用-圖片輪播實現

編輯:關於Android編程

前一篇寫到Android事件分發機制學習筆記,下面我們通過一個實例的應用來實踐理解下Android事件分發的機制。我們這裡來實現一個圖片的輪播功能,最後順便實現下圖片的自動輪播。

我們的圖片輪播是封裝在一個ViewGroup裡,當我們進行橫向滑動的時候,我們需要阻止事件從ViewGroup往子控件分發,ViewGroup來消費我們當前的滑動圖片何去何從。下面我們貼出我們的封裝的ViewGroup的代碼實現如下:

 

public class ImageSwitcher extends ViewGroup {
	
	private String TAG = ImageSwitcher.class.getSimpleName();
	private static final int SNAP_VELOCITY = 300;

	private Scroller scroller;
	
	private VelocityTracker mVelocityTracker;
	
	private int mTouchSlop;

	private float mMotionX;

	private int mImageWidth; 
	
	private int imageCount;

	private int mIndex;

	private int mImageHeight;
	
	private int[] imageItems;
	
	private boolean forceToRelayout;
	
	private int mTouchState = TOUCH_STATE_REST;
	
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_SCROLLING = 1;
	
	private static final int AUTO_MSG = 0;
	
	private static final int START_MSG =2;
	
	private static final int HANDLE_MSG = 1;
	
	private static final long PHOTO_CHANGE_TIME = 4000;
	
	
	private  Handler mHandler = new Handler(){ //處理圖片自動或者手動滾動操作
		
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case AUTO_MSG:
				scrollToNext();
				mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
				break;
			case START_MSG:
				mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
				break;
			case HANDLE_MSG:
				mHandler.removeMessages(AUTO_MSG);
				mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
			default:
				break;
			}
		}
	};

	/**
	 * 表示滾動到下一張圖片這個動作
	 */
	private static final int SCROLL_NEXT = 0;
	/**
	 * 表示滾動到上一張圖片這個動作
	 */
	private static final int SCROLL_PREVIOUS = 1;
	
	private static final int SCROLL_BACK = 2;
	
	
	public ImageSwitcher(Context context, AttributeSet attrs) {
		super(context, attrs);
		scroller = new Scroller(context);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}
	
	/**
	 * 當View被添加到Window容器的時候才開始執行:生命周期依次先後 onMeasure > onLayout > onDraw >onAttachedToWindow 
	 */
	@Override
	protected void onAttachedToWindow(){
		super.onAttachedToWindow();
		mHandler.sendEmptyMessage(START_MSG); //發送消息讓圖片自動開始滾動
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if(changed || forceToRelayout){
			imageCount = getChildCount();
			mImageWidth = getMeasuredWidth();
			mImageHeight = getMeasuredHeight();
			int marginLeft = 0;
			scroller.abortAnimation(); //設置scroller為滾動狀態
			this.scrollTo(0, 0); //每次重新布局時候,重置滾動初始位置
			int[] items = { getIndexForItem(1), getIndexForItem(2),
					getIndexForItem(3), getIndexForItem(4),
					getIndexForItem(5) };
			imageItems = items;
			for (int i = 0; i < items.length; i++) {
				ImageView childView = (ImageView)getChildAt(items[i]);
				childView.layout(marginLeft, 0, marginLeft
						+ mImageWidth , mImageHeight);
				marginLeft = marginLeft + mImageWidth;
			}
			refreshImageView();
			forceToRelayout = false;
		}
	}
	
	private void refreshImageView(){
		for (int i = 0; i < imageItems.length; i++) {
			ImageView childView = (ImageView)getChildAt(imageItems[i]);
			childView.invalidate();
		}
	}
	
	private int getIndexForItem(int item) {
		int index = -1;
		index = mIndex + item - 3;
		while (index < 0) {
			index = index + imageCount;
		}
		while (index > imageCount - 1) {
			index = index - imageCount;
		}
		return index;
	}
	
    @Override
	public boolean  onInterceptTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;		
		}
		float xLoc = ev.getX();
		switch(action){
		case MotionEvent.ACTION_DOWN:
			mMotionX = xLoc;
			mTouchState = TOUCH_STATE_REST;
			Log.e(TAG, onInterceptTouchEvent ACTION_DOWN);  
			break;
		case MotionEvent.ACTION_MOVE:
			Log.e(TAG, onInterceptTouchEvent ACTION_MOVE);  
			 int xDif = (int)Math.abs(mMotionX - xLoc);
			 if(xDif > mTouchSlop){  //當我們的水平距離滾動達到我們滾動的最小距離,開始攔截ViewGroup的事件給子控件分發
				 mTouchState = TOUCH_STATE_SCROLLING;
			 }
			break;
		case MotionEvent.ACTION_UP:
			Log.e(TAG, onInterceptTouchEvent ACTION_UP);  
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			Log.e(TAG, onInterceptTouchEvent ACTION_CANCEL);
			mTouchState = TOUCH_STATE_REST;
			break;
		default:
			Log.e(TAG, onInterceptTouchEvent DEFAULT);
			mTouchState = TOUCH_STATE_REST;
		    break;
		}
		return mTouchState != TOUCH_STATE_REST;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if(scroller.isFinished()){ //scroller還沒有開始或者已經完成,以下代碼在手指滑動的時候才開始執行
			if (mVelocityTracker == null) {
				mVelocityTracker = VelocityTracker.obtain();
			}
			mVelocityTracker.addMovement(event);
			int action = event.getAction();
			float x = event.getX();
			switch (action) {
			case MotionEvent.ACTION_DOWN:
				// 記錄按下時的橫坐標
				mMotionX = x;
			case MotionEvent.ACTION_MOVE:
				int disX = (int)(mMotionX - x);
				mMotionX = x;
				scrollBy(disX, 0);
				break;
			case MotionEvent.ACTION_UP:
				mVelocityTracker.computeCurrentVelocity(1000);
				int velocityX = (int) mVelocityTracker.getXVelocity();
				if (judeScrollToNext(velocityX)) {
					// 下一張圖
					scrollToNext();
				} else if (judeScrollToPrevious(velocityX)) {
					//上一張圖
					scrollToPrevious();
				} else {
					// 當前圖片
					scrollBack();
				}
				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
				mHandler.sendEmptyMessageDelayed(HANDLE_MSG, PHOTO_CHANGE_TIME);
				return true;
			}
		}
		return false; 
	}
	

	private void scrollBack() {
		if (scroller.isFinished()) {
			beginScroll(getScrollX(), 0, -getScrollX(), 0,SCROLL_BACK);
		}
	}

	private void scrollToPrevious() {
		if(scroller.isFinished()){
			setImageSwitchIndex(SCROLL_PREVIOUS);
			int disX = -mImageWidth - getScrollX();
			beginScroll(getScrollX(), 0, disX, 0,SCROLL_PREVIOUS);
		}
	}

	private void scrollToNext() {
		if (scroller.isFinished()) {
			setImageSwitchIndex(SCROLL_NEXT);
			int disX = mImageWidth - getScrollX();
			beginScroll(getScrollX(), 0, disX, 0,SCROLL_NEXT);
		}
	}
	
	/**
	 * 圖片開始滑動
	 */
	private void beginScroll(int startX, int startY, int dx, int dy,
			final int action) {
		int duration = (int) (700f / mImageWidth * Math.abs(dx));
		scroller.startScroll(startX, startY, dx, dy, duration);
		invalidate();
		mHandler.postDelayed(new Runnable() {
			@Override
			public void run() {
				if (action == SCROLL_NEXT || action == SCROLL_PREVIOUS) {
					forceToRelayout = true;
					requestLayout();
				}
			}
		}, duration);
	}

	private void setImageSwitchIndex(int action) {
		if(action == SCROLL_NEXT){
			if(mIndex < imageCount){
				mIndex++;
			}else{
				mIndex = 0;
			}
		}else if(action == SCROLL_PREVIOUS){
			if(mIndex > 0){
				mIndex--;
			}else{
				mIndex = imageCount -1;
			}
		}
	}

	/**
	 * 判斷時候滑向前一個
	 * @param velocityX
	 * @return
	 */
	private boolean judeScrollToPrevious(int velocityX) {
		return velocityX > SNAP_VELOCITY || getScrollX() < -mImageWidth / 2;
	}

	/**
	 * 判斷時候滑向後一個
	 * @param velocityX
	 * @return
	 */
	private boolean judeScrollToNext(int velocityX) {
		return velocityX < -SNAP_VELOCITY|| getScrollX() > mImageWidth / 2;
	}
	
	@Override
	public void computeScroll() {	
		if (scroller.computeScrollOffset()) {
			scrollTo(scroller.getCurrX(), scroller.getCurrY());
		    //刷新View 否則效果可能有誤差
			postInvalidate();
		}
	}
}

 

從代碼分析我們知道,我們在判斷手指事件的時候,我們在ACTION_MOVE的時候,我們判斷當水平移動距離可判為大於移動的最小距離,我們這個時候攔截VIewGroup往下分發的事件,事件這個時候會走ViewGroup的onTouchEvent來消費事件,我們把讀圖片的輪滑處理在onTouchEvent裡來處理。

這個Demo裡我們要注意的一點,我們來看我們的布局文件如下:

 


 	
        
        
        
        
         
    
我們看到我們對ImageView設置了clickable = true的屬性,如果這裡我們去掉該屬性,我們就不能手動去滑動圖片了。

 

大家一定會好奇這是為什麼?下面我們來根據前一篇的Android事件分發機制學習筆記 的事件分發分析來做出解答。我們通過分析,如果我們不給ImageView設置clickable=true,ImageView的onEventTouch嘗試消費時候會發現 if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) 進到判斷裡去,onEventTouch 返回值為false,那ImageView的dispatchTouchEvent返回為false,意思就是ImageView控件是默認不消費事件的。還記得我們在Android的事件分發中提到,當我們的一次事件從Activity開始分發到葉子控件,到葉子控件開始一層一層回溯嘗試消費事件,還記得我們上次說的事件“記憶”功能,當我們從Activity分發的事件一直回溯到Activity都沒有被消費掉,後面的事件就不會從根控件DecorView繼續往下分發。

下面我們要問,那我們不給ImageView 設置clickable = true,有沒有辦法讓圖片可以滑動呢?答案當然有,辦法一,我們這裡用Button來代替ImageVIew;辦法二,我們ImageView沒有消費掉事件,我們的事件就會回溯到ViewGroup去嘗試消費,我們可以在VIewGroup的onTouchEvent去消費,通過修改onTouchEvent的返回值為true,來達到消費的效果。那我們後面的動作也就會記住事件分發消費“回路”,我們的後續事件也就能得到消費,我們方法二的辦法就是,在ViewGroup裡去返回onTouchEvent的返回值來消費,此處我們完全可以把事件攔截onInterceptTouchEvent注釋掉,同樣達到我們上面代碼的效果。以上是對圖片輪播的事件處理過程的主要講解,代碼裡還加入處理圖片自動輪播的代碼。

後面有時間,我會來嘗試分析更復雜的事件分發的過程,比如,ListView的基類AbsLIstView已經加入了自己的事件分發、攔截處理,我們怎麼對ListView做我們自己的事件分發攔截處理,歡迎大家來拍磚。最後附上圖片輪播例子的Demo.

 

 

 


 

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