Android的事件分发和处理方式
  对android开发有一定了解的同学一定或多或少知道android的触摸事件分发,整个事件的分发消耗流程都可以通过看源码理解,下面通过讲解demo帮助加深事件分发的理解和在实战中的应用。首先直接上demo截图:

  demo布局
  整个首页布局是这样的,最外层是ViewPager,里面包含四个子功能,每个子功能的视图都是一个Fragment。“功能1”里的列表项是一个GridView,此gridview外层是带有LinearLayout的ScrollView。
  布局如下

  难点分析和讲解
  一、GridView高度问题
  二、长按GridView某一项可以替换位置,最后一项“更多”不参与滑动与替换位置
  三、GridView长按并滑动某一项时,滑动过程中与ScrollView和ViewPager冲突问题
  四、滑动GridView中某一项时,当滑动到顶部时ScrollView要能向下滚动;手指滑动到底部时要能项上滚动
  解决问题
  解决问题一
  为了解决这一系列问题,我需要重写DragGridView继承GridView,第一步需要让DragGridView的每一项item占满整个视图,而gridview默认是限定高度的,解决办法是重写onMeasure,修改测量高度方法,如下
Java代码
    - @Override  
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
-     int expandSpec = View.MeasureSpec.makeMeasureSpec(  
-             Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);  
-     super.onMeasure(widthMeasureSpec, expandSpec);  
- }  
  解决问题二
  现在我们需要长按GridView某一项可以替换位置,思路是首先需要捕捉到长按事件,那么怎么算长按事件呢?手指快速的从item上划是不算的,手指按下然后很快离开这是点击事件也不能算长按。只有手指在item上按下并且停留在item上一定时间才能算长按。有了思路,下面看关键的几处代码:
Java代码
    - private Handler mHandler = new Handler();  
-   
- private Runnable mLongClickRunnable = new Runnable() {  
-   
-     @Override  
-     public void run() {  
-       
-     }  
- };  
-   
- @Override  
- public boolean dispatchTouchEvent(MotionEvent ev) {  
-     switch(ev.getAction()){  
-         case MotionEvent.ACTION_DOWN:  
-               
-             mHandler.postDelayed(mLongClickRunnable, dragResponseMS);  
-             break;  
-         case MotionEvent.ACTION_MOVE:  
-               
-             if(!isTouchInItem(mStartDragItemView, moveX, moveY)){  
-                 mHandler.removeCallbacks(mLongClickRunnable);  
-                 mHandler.removeCallbacks(mScrollRunnable);  
-             }  
-   
-             break;  
-         case MotionEvent.ACTION_UP:  
-         case MotionEvent.ACTION_CANCEL:  
-             mHandler.removeCallbacks(mLongClickRunnable);  
-             break;  
-     }  
-     return super.dispatchTouchEvent(ev);  
- }  
  解析: 定义一个Handler,重写ViewGroup(这里即DragGridView)分发事件方法dispatchTouchEvent(),在接受到ACTION DOWN事件时handler向消息队列会延迟发送一条消息,延迟时间就是长按时间的阀值,当接受到消息时说明长按事件已成立,如果手指在消息延迟期间滑走或移出则取消消息。
  有了长按事件如何滑动某一条item呢?如何替换两个item位置呢?
  这里的解决思路是,当其中的某项假设是item1接收到长按事件后,先隐藏item1,在item1的位置上新建一个和item1长相位置都一样view假设叫它litem,手指滑动时对litem跟随滑动,接着重点来了,当手指滑动到item2区域后发出一个替换事件。这样我们就成功将两个item替换了位置并且用户体验比较好,代码片段如下:
Java代码
    -    
- private Runnable mLongClickRunnable = new Runnable() {  
-   
-     @Override  
-     public void run() {  
-         if (onDragStartListener != null) {  
-             onDragStartListener.onDragStart();  
-         }  
-         isDrag = true;   
-         mVibrator.vibrate(50);   
-         mStartDragItemView.setVisibility(View.INVISIBLE);  
-           
-         createDragImage(mDragBitmap, mDownX, mDownY);  
-   
-   
-     }  
- };  
-  
-  
-  
-   
- public void setOnChangeListener(OnChangeListener onChangeListener){  
-     this.onChangeListener = onChangeListener;  
- }  
-  
-  
-  
-  
-  
-  
-  
-   
- private void createDragImage(Bitmap bitmap, int downX , int downY){  
-     mWindowLayoutParams = new WindowManager.LayoutParams();  
-     mWindowLayoutParams.format = PixelFormat.TRANSLUCENT;   
-     mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;  
-     mWindowLayoutParams.x = downX - mPoint2ItemLeft + mOffset2Left;  
-     mWindowLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top - mStatusHeight;  
-     mWindowLayoutParams.alpha = 0.55f;   
-     mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  
-     mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  
-     mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
-             | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ;  
-   
-     mDragImageView = new ImageView(getContext());  
-     mDragImageView.setImageBitmap(bitmap);  
-     mWindowManager.addView(mDragImageView, mWindowLayoutParams);  
- }  
- @Override  
- public boolean onTouchEvent(MotionEvent ev) {  
-     if(isDrag && mDragImageView != null){  
-         switch(ev.getAction()){  
-             case MotionEvent.ACTION_MOVE:  
-                 moveX = (int) ev.getX();  
-                 moveY = (int) ev.getY();  
-                 moveRawY = (int) ev.getRawY();  
-                   
-                 onDragItem(moveX, moveY);  
-                 break;  
-             case MotionEvent.ACTION_UP:  
-                 onStopDrag();  
-                 isDrag = false;  
-                 break;  
-         }  
-         return true;  
-     }  
-     return super.onTouchEvent(ev);  
- }  
  解析:在接收到长按事件后会调用 createDragImage(mDragBitmap, mDownX, mDownY)方法,这是为了在item的位置上创建一个长相一样的view,当手指滑动时在onTouch()方法中监听move事件处理view的位置
  解决问题三
  为题三的解决比较简单,当gridview接收到长按事件后,处理ViewPager和ScrollView的方法一样requestDisallowInterceptTouchEvent(false); 这个方法是让ViewGroup本身不拦截触摸事件。当gridView滑动结束在requestDisallowInterceptTouchEvent(true);
  解决问题四
Java代码
    - public class ControlScrollView extends ScrollView {  
-   
- private boolean isInControl = true;  
- private int moveSpeed = 5;  
- private final int msgWhat = 1;  
- private final int time = 20;  
- private ScrollState scrollState;  
-   
- public ControlScrollView(Context context) {  
-     super(context);  
-     init();  
- }  
-   
- public ControlScrollView(Context context, AttributeSet attrs) {  
-     super(context, attrs);  
-     init();  
- }  
-   
- @Override  
- public boolean dispatchTouchEvent(MotionEvent ev) {  
-     switch (ev.getAction()) {  
-         case MotionEvent.ACTION_MOVE:  
-             if (!isInControl) {  
-                 if (ev.getY() < 0) {  
-                     if (!myHandler.hasMessages(msgWhat)) {  
-                         Message msg = new Message();  
-                         msg.arg1 = -1;  
-                         msg.what = msgWhat;  
-                         myHandler.sendMessageDelayed(msg, time);  
-                     }  
-                     return super.dispatchTouchEvent(ev);  
-                 } else if (ev.getY() > getHeight()) {  
-                     if (!myHandler.hasMessages(msgWhat)) {  
-                         Message msg = new Message();  
-                         msg.arg1 = 1;  
-                         msg.what = msgWhat;  
-                         myHandler.sendMessageDelayed(msg, time);  
-                     }  
-                     return super.dispatchTouchEvent(ev);  
-                 } else {  
-                     myHandler.removeMessages(msgWhat);  
-                 }  
-             } else {  
-                 myHandler.removeMessages(msgWhat);  
-             }  
-             break;  
-         case MotionEvent.ACTION_UP:  
-         case MotionEvent.ACTION_CANCEL:  
-             if (scrollState != null) {  
-                 scrollState.stopTouch();  
-             }  
-             myHandler.removeMessages(msgWhat);  
-             requestDisallowInterceptTouchEvent(false);  
-             break;  
-     }  
-     return super.dispatchTouchEvent(ev);  
- }  
-   
-   
- @Override  
- protected void onDetachedFromWindow() {  
-     super.onDetachedFromWindow();  
-     myHandler.removeMessages(msgWhat);  
- }  
-   
-   
- private void init() {  
-     moveSpeed = ScreenUtils.dip2px(getContext(), moveSpeed);  
- }  
-   
- public boolean isInControl() {  
-     return isInControl;  
- }  
-   
- public ScrollState getScrollState() {  
-     return scrollState;  
- }  
-   
- public void setScrollState(ScrollState scrollState) {  
-     this.scrollState = scrollState;  
- }  
-   
- public void setInControl(boolean inControl) {  
-     isInControl = inControl;  
- }  
-   
- private Handler myHandler = new Handler() {  
-     @Override  
-     public void dispatchMessage(Message msg) {  
-         super.dispatchMessage(msg);  
-         smoothScrollBy(0, moveSpeed * (msg.arg1 > 0 ? 1 : -1));  
-         Message msg1 = new Message();  
-         msg1.what = msg.what;  
-         msg1.arg1 = msg.arg1;  
-         myHandler.sendMessageDelayed(msg1, time);  
-     }  
- };  
-   
- public interface ScrollState {  
-     void stopTouch();  
- }  
  解析:大致逻辑就是判断手指位置,超出边界发送消息对scrollview进行滚动
  结束
  源码地址 https://github.com/halibobo/TouchListenerConflict