前一篇写到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