您的位置:首页 > 移动开发 > Android开发

Android事件分发学习应用-图片轮播实现

2015-02-11 19:52 756 查看
前一篇写到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里我们要注意的一点,我们来看我们的布局文件如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 	<com.zhanglei.imageswitcher.ImageSwitcher
        android:id="@+id/image_switch_view"
        android:layout_width="match_parent"
        android:layout_height="200dp" >
        <ImageView
            android:id="@+id/image1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:clickable="true"
            android:src="@drawable/item01"/>
        <ImageView
            android:id="@+id/image2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:clickable="true"
            android:src="@drawable/item02"/>
        <ImageView
            android:id="@+id/image3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:clickable="true"
            android:src="@drawable/item03"/>
        <ImageView
            android:id="@+id/image4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:clickable="true"
            android:src="@drawable/item04"/>
         <ImageView
            android:id="@+id/image5"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:clickable="true"
            android:src="@drawable/item05"/>
    </com.zhanglei.imageswitcher.ImageSwitcher>
</RelativeLayout>
我们看到我们对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.

转载请注明出处:http://blog.csdn.net/johnnyz1234/article/details/43737455
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: