Touch的传递机制(Intercept不一定每次都调用)
当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View即事件总是先传递Activity,Activity再传递给Window,最后Window再传递给顶级View,顶级View接收到事件后,就会按照事件分发机制去分发事件
(1)ViewGroup默认不拦截任何事件,Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false
(2)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个view是disable状态的,只要他的clicklable或者longClickable有一个为true,那么他的onTouchEvent就返回True
(3)如果View不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其他事件都不会再交给它来处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent会被调用。(打个比方如果交给下属一件小事都没处理好,那就不会继续把更大的事交给它处理)
(4)某个view一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给他的话),并且它的onInterceptTouchEvent不会再被调用(当一个view决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用在调用这个view的onInterceptTouchEvent去询问他是否要拦截了)
(5)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外
当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件,可以从源码看出来了
public boolean dispatchTouchEvent(MotionEvent ev) { 。。。。。。 boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState();//会对FLAG_DISALLOW_INTERCEPT充值 } 。。。。。。
<p style="background:rgb(43,43,43);"><span style="color:rgb(255, 255, 255);">}</span></p><p>}</p>
因此子View调用requestDisallowInterceptTouchEvent方法并不能影响ViewGroup对ACTION_DOWN对事件的处理
(7)通过setClickable和setLongClickabe分别改变view的CLICKABLE和LONG_CLICKABLE属性,另外,setOnClickListener会自动将View的CLICKABLE设为true,setOnLongClickListener则会自动将View的LONG_CLICKABLE设为True
自最后的滑动的冲突事件的解决 用MySlideMenu(已写完)加上zhbj的框架做例子(未写完)
//SlideMenuView.java
/** * Created by Administrator on 2015/12/13 0013. */ public class SlideMenuView extends RelativeLayout { private LinearLayout mLeft; private LinearLayout mContent; private int mLeftMeasureWidth; private float startX; private float downX; private float moveX; private float downY; private float moveY; private Scroller mScroller; boolean isLeftOpened = false; private int downX2; private int downY2; public SlideMenuView(Context context) { super(context); } public SlideMenuView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } public SlideMenuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * Finalize inflating a view from XML. This is called as the last phase of inflation, * after all child views have been added. */ @Override protected void onFinishInflate() { super.onFinishInflate(); mLeft = (LinearLayout) getChildAt(0); mContent = (LinearLayout) getChildAt(1); //获取left的宽度 mLeftMeasureWidth = mLeft.getLayoutParams().width; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //slideMenu是一个viewgroup //所以要先写子view的measure //1.conent的宽高跟父容器(也就是slidemenu一样) mContent.measure(widthMeasureSpec, heightMeasureSpec); //2.mleft的宽是80dp,高和父容器一样 //好像还有一种就是upspecified 也就是布局文件写多少就是多少?????? int mLeftMeasureSpec = MeasureSpec.makeMeasureSpec(mLeftMeasureWidth, MeasureSpec.EXACTLY); mLeft.measure(mLeftMeasureSpec, heightMeasureSpec); //3.最后就到自己了 int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(measuredWidth, measuredHeight); // 因为自己的大小没有变可以直接写下面的就行,但是这里暂时写上面的 // super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // super.onLayout(changed, l, t, r, b); //1.先放置mLeftView // mLeft.layout(-mLeft.getMeasuredWidth() + 100, 0, 100, mLeft.getMeasuredHeight());//这样可以露一点 // mContent.layout(100, 0, mContent.getMeasuredWidth()+100, mContent.getMeasuredHeight()); mLeft.layout(-mLeft.getMeasuredWidth(), 0, 0, mLeft.getMeasuredHeight()); //2.在放置mContent mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight()); } /** * 避免滑动出现的几种极端情况 */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("xcqw onTouchEvent ACTION_DOWN"); downX = event.getRawX(); downY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: moveX = event.getRawX(); moveY = event.getRawY(); System.out.println("xcqw ACTION_MOVE"); float dx = downX - moveX; //The left edge of the displayed part of your view, in pixels //可以理解就是现在窗体的位置 int verteX = getScrollX(); System.out.println("xcqw verteX" + verteX); System.out.println("xcqw dx" + dx); //情况1 就是窗体最多移动-mLeft.getMeasuredWidth() if (verteX + dx < -mLeft.getMeasuredWidth()) { //窗体直接移到-mLeft.getMeasuredWidth()!!! //Set the scrolled position of your view scrollTo(-mLeft.getMeasuredWidth(), 0); System.out.println("xcqw 移到最左边"); } else if (verteX + dx > 0) { //情况2 就是窗体右移最多智能移到mContent.getMeasureWidth System.out.println("xcqw 移到Content"); scrollTo(0, 0); } else { //在原有位置上移动 System.out.println("xcqw 在当前位置移动"); scrollBy((int) dx, 0); } //更新坐标 downX = moveX; downY = moveY; break; case MotionEvent.ACTION_UP: System.out.println("xcqw ACTION_UP"); //根据移动到的位置来做自动开关 //如果打开位置小于一半就关上 or 大于一半 就自动打开 System.out.println("xcqw action_up 开还是关" + (getScrollX() < -mLeft.getMeasuredWidth() / 2)); judgeOpenOrClose(getScrollX() < -mLeft.getMeasuredWidth() / 2); break; } return true; //深究一下为什么会是return false // return super.onTouchEvent(event); } private void judgeOpenOrClose(boolean open) { isLeftOpened = open; if (open) { //如果大于二分之一,就自动打开 //The left edge of the displayed part of your view, in pixels. int startX = getScrollX(); //The top edge of the displayed part of your view, in pixels. int startY = getScrollY(); int endX = -mLeft.getMeasuredWidth(); int endY = 0; int dx = endX - startX; int dy = endY - startY; //根据移动的px来设定滑动的速度(控制总共的时间),这样体验比较好 int duration = Math.abs(dx) * 10; if (duration > 500) { duration = 500; } mScroller.startScroll(startX, startY, dx, dy, duration); } else { //如果小于二分之一,就自动关闭 //The left edge of the displayed part of your view, in pixels. int startX = getScrollX(); //The top edge of the displayed part of your view, in pixels. int startY = getScrollY(); int endX = 0; int endY = 0; int dx = endX - startX; int dy = endY - startY; //根据移动的px来设定滑动的速度(控制总共的时间),这样体验比较好 int duration = Math.abs(dx) * 10; if (duration > 500) { duration = 500; } mScroller.startScroll(startX, startY, dx, dy, duration); } //重新绘制 draw-->ondraw invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), 0); postInvalidate(); } } public void openLeft() { //打开左边菜单 judgeOpenOrClose(true); } public void closeLeft() { //关闭左边菜单 judgeOpenOrClose(false); } //避免left的scroll吃掉左右滑动事件 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downX = getX(); downY = getY(); System.out.println("xcqw onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: moveX = getX(); moveY = getY(); float dx = downX - moveX; float dy = downY - moveY; if (Math.abs(dx) > Math.abs(dy)) { //如果x方向移动大于y方向 //不分发了//自己处理 return true; } break; case MotionEvent.ACTION_UP: break; } return super.onInterceptTouchEvent(ev); } //为了解决当left全部脱出时点击content区域关闭left(也就是让他缩回去) @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX2 = (int) event.getX(); //这里如果写downY会有问题 downY 跟 upY差太多//因为前面有个donwnY = moveY downY2 = (int) event.getY(); System.out.println("xcqw dispatchTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: float upX = event.getX(); float upY = event.getY(); System.out.println("xcqw dispatchTouchEvent ACTION_UP" + isLeftOpened); if (isLeftOpened) { //因为像素点很小不可能每次按都是按同一个像素点,所以要给一个误差范围 System.out.println("xcqw dispatchTouchEvent ACTION_UP upX-" + upX + "-downX-" + downX); System.out.println("xcqw dispatchTouchEvent ACTION_UP upY-" + upY + "-downY-" + downY); System.out.println("xcqw dispatchTouchEvent ACTION_UP upX-" + upX + "-downX2-" + downX2); System.out.println("xcqw dispatchTouchEvent ACTION_UP upY-" + upY + "-downY2-" + downY2); if ((upX < downX2 + 4) && (upX > downX2 - 4) && upY < downY2 + 4 && upY > downY2 - 4) { System.out.println("xcqw dispatchTouchEvent ACTION_UP x1"); //排除用户按住滑动然后放手 //点击位置跟弹起位置在一定范围内就认为是点了不是移动 //这样就可以排除了按着滑动一定距离的情况 //关闭left if (upX > mLeft.getMeasuredWidth()) { //这样是在left全部展开的情况点击关闭 //关闭左侧 System.out.println("xcqw dispatchTouchEvent ACTION_UP x2"); judgeOpenOrClose(false); //自己就把他处理,不要别人来处理 return true; } } } break; } return super.dispatchTouchEvent(event); } public boolean isLeftOpen() { return isLeftOpened; } }
<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" tools:context=".MainActivity"> <com.dx.text.myslidemenu.SlideMenuView android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/left"/> <include layout="@layout/content"/> </com.dx.text.myslidemenu.SlideMenuView> </RelativeLayout>