好久没有写博客了,呵呵,我做事貌似总是这么有一段没一段的,前几天我看好多人在理解的事件分发和处理的时候都把QQ侧滑拿出来分析分析,同样今天我也是把QQ侧滑拿出来分析一下,但因为我是已经知道了答案,所以呢,我就用倒序的方式来聊一聊Android中事件处理。
1. View中的事件分发顺序
注意:如果返回值为true,就表示,已经消费了该事件,该事件便不会继续往下传递
dispatchTouchEvent(MotionEvent event);
|
|
mOnTouchListener.onTouch(this, event);
|
|
onTouchEvent(event);
|
|
onClick(this);
看看源码:
2.ViewGroup中的事件分发顺序
注意:ViewGroup是View的组合,View的事件处理中没有对多个View情况,但是ViewGroup中有,所以就多了一个方法,叫事件拦截,具体看下面得流程图
dispatchTouchEvent(MotionEvent ev);
|
|
onInterceptTouchEvent(ev);
|
|
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign);
如果父View不拦截事件,父View就要把事件分发给子view,子view要判断
他还有没有子view,如果他有则继续分发,没有则重复View的事件分发的顺序
看看源码:
那么事件拦截又是一个什么鬼?
刚刚已经把View和ViewGroup的事件分发顺序,这是按照正常的执行情况下,View对于触摸事件的处理,那么所谓的事件拦截又从何说起呢?
这里简单的分为两种情况
1.子view请求父View拦截事件
比如,之前自己实现侧滑菜单和ViewPager联用时,滑动到左侧时,应该弹出侧滑菜单,这个时候Viewpager应该请求父View拦截事件,不让自己处理。
看源码是如何实现请求父View拦截的
2.父View不让事件传递下去
这个就比较简单了,你可以在事件分发顺序中的那几个方法返回true就行
说了原理 那现在就把QQ侧滑的效果实现一下
步骤
1.继承HorizontalScrollView,因为HorizontalScrollView已经帮我们把View摆放好了,所以用它更方便
获取屏幕的宽度,用于判断两个View滑到了哪一个区域该左滑还是右滑
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
2.重写onMeasure()方法
获取布局中的两个布局,然后分别指定他们的宽度(菜单选项的宽度等于屏幕宽度-固定值,内容宽度等于屏幕的实际宽度)
if (!mIsOnce) {
LinearLayout childAt = (LinearLayout) getChildAt(0);
mMenu = childAt.getChildAt(0);
mMain = childAt.getChildAt(1);
mMenuWidth = mScreenWidth - 200;
mMenu.getLayoutParams().width = mMenuWidth;
mMain.getLayoutParams().width = mScreenWidth;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
3.重写onLayout()方法
将其移动到指定位置,使用this.scrollTo()方法
为了避免每次都要测量,这里是刚进入app的时候摆放,所以在这里用一个变量记着,下次onMeasure的时候就不用测量了
if (changed) {
this.scrollTo(mMenuWidth, 0);
mIsOnce = true;
}
super.onLayout(changed, l, t, r, b);
4.重写onTouchEvent()方法
记录down时的坐标,在move时再获取一次坐标,两个值相减是否大于某一固定值,移动到相对位置
因为onTouchEvent方法已经消费了事件,所以这里得返回true,这里也就是事件拦截;
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
break;
case MotionEvent.ACTION_UP: {
float dx = ev.getX() - downX;
if (dx < mScreenWidth / 3) {
this.smoothScrollTo(mMenuWidth, 0);
} else {
this.smoothScrollTo(0, 0);
}
return true;
}
}
return super.onTouchEvent(ev);
}
5.在onScrollChanged()方法中做一些在移动中所需要做的效果
//滑动的时候进行动画处理
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float factor = (float) l / mMenuWidth;
mMenu.setTranslationX(mMenuWidth * factor * 0.6f);
float leftScale = 1 - 0.4f * factor;
mMenu.setScaleX(leftScale);
mMenu.setScaleY(leftScale);
float rightScale = 0.8f + 0.2f * factor;
mMain.setScaleX(rightScale);
mMain.setScaleY(rightScale);
mMenu.setAlpha(1 - factor);
}
效果我就不展示了,我只是想把事件分发自己彻底弄明白而已。。。希望你我共勉