微信公众号:Lucidastar
如有问题或建议,请公众号留言
最近更新:2018-04-03
View的认识
1、View的位置参数
width = right - left
height = bottom - top
Left = getLeft();
Right = getRight();
Top = getTop();
Bottom = getBottom();
从Android3.0开始,View增加了额外的几个参数。x、y、translationX和translationY,其中x和y是View左上角的坐标,translationX和translationY是View左上角相当于父容器的偏移量。translationX和translationY默认值是0,
x = left + translationX;
y = top + translationY;
left和top表示的是原始左上角的位置信息,其值是不会改变,改变的是x、y、translationX和translationY这四个参数。
2、MotionEvent和TouchSlop
- MotionEvent对象可以得到点击事件发生的x和y的坐标。
- getX/getY和getRawX/getRawY。
getX/getY返回的是相当于当前View左上角的x和y的坐标
getRawX/getRawY返回的是相当与手机屏幕左上角的x和y的坐标
TouchSlop是系统所能识别的被认为是滑动的最小距离
3、VelocityTracker、GestureDetector和Scroller
- VelocityTracker 速度追踪,包括水平和竖直
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
- GestureDetector 手势监测
GestureDetector mDestureDetector = new GestureDetector (this);
mDestureDetector.setIsLongpressEnabled(false);//解决长按屏幕后无法拖动的现象
boolean consume = mDestureDetector.onTouchEvent(event);
return consume;
- Scroller弹性滑动对象,用于实现View的弹性滑动
Scroller scroller = new Scroller(mContext);
4、View的滑动
可以通过三种方式进行滑动
- 是通过View本身提供的scrollTo/scrollBy方法来实现滑动;
- 通过动画给View施加平移效果来实现滑动
- 通过改变View的layoutParams使得View重新布局从而实现滑动
View的事件分发机制
View的点击分发过程由三个很重要的方法来公共完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent;
public boolean dispatchTouchEvent(MotionEvent ev)
//用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
//在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
//在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
下面用伪代码表示一下他们的关系
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){ //拦截了
consume = onTouchEvent(ev);//判断onTouchEvent是否消耗了
}else{//没有拦截,交给子View继续事件的分发
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
1、分发 2、是否拦截3、是否消耗 没有拦截就交个子View继续分发,拦截了就自己处理。
当一个点击事件产生后,它的传递过程遵循如下顺序Activity-->Window-->View
事件传递过程是由外向内的,即事件总先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
Activity对点击事件的分发过程
Activity#dispatchTouchEvent getWindow().superDispatchTouchEvent(ev)
Window#superDispatchTouchEvent(ev) 抽象类
PhoneWindow#superDispatchTouchEvent(ev) 具体实现Window mDecor.superDispatchTouchEvent(event) mDecor是一个view应该说是一个FrameLayout
这样就把事件传递到了顶级的View了。
View的滑动冲突
常见冲突场景
- 场景1---外部滑动方向和内部滑动方向不一致
- 场景2---外部滑动方向和内部滑动方向一致
- 场景3---上面两种情况的嵌套
自定义一个水平滑动的容器(ViewGroup)
当给里头放listView时,为了解决水平和竖直滑动的冲突,该如何进行解决??
①首先解决的思路是外部拦截法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercept = true;
}
break;
case MotionEvent.ACTION_MOVE:
int delayX = x - mLastXIntercept;
int delayY = y - mLastYIntercept;
if (Math.abs(delayX) > Math.abs(delayY)) {
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
mLastYIntercept = y;
mLastXIntercept = x;
mLastX = x;
mLastY = y;
return intercept;
}
首先父容器会拦截到事件
- 按下,父容器肯定不会拦截,如果拦截了,那么后续的滑动和抬起都会交由父容器处理,这个时候事件没法再传递给子元素了。
- 滑动,这个事件可以根据需要来决定是否拦截。根据我们的需要,通过左右和上下滑动的大小来判读是否拦截,当我们是左右滑动时就需要拦截,反 之不拦截,因为我们的需求就是左右滑动时就交由父容器来处理。
- 抬起,抬起时,父容器就不管了,由子元素来做处理就可以了
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy(-deltaX,0);
break;
case MotionEvent.ACTION_UP://主要做一些child的位置判定
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50){
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
}else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
当父容器拦截成功了,我就把事件进行了消耗。
②内部拦截法
重写listView,复写dispatchTouchEvent,因为父容器除了按下不拦截,其他的事件都会拦截掉,不拦截时就把事件进行分发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
当按下的时候,告诉父容器不拦截mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
当滑动的时候,当判断是左右滑动的时候,告诉父容器进行拦截mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
这样解决了滑动冲突,这就是内部拦截法
看完第三章的总结
- View的位置坐标了解
- 方法及参数的了解(getX()、getY()、getRawX()、getRawY() 、translationX translationY)
- 理解MotionEvent TouchSlop
- VelocityTracker(速度) 、GestureDetector(手势)、Scroller(弹性滑动)
- View的滑动方式(3种)
- 事件分发机制,三个方法的理解及它们的关系(dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent)分发,拦截及消费。可以看上面的伪代码
- 点击事件的分发过程(由外向内的)看源码理解一下
- 通过例子来理解分发过程及处理事件的冲突的解决方案
网友评论