1.View的位置参数top(左上角纵坐标),left(左上角横坐标),bottom(右下角纵坐标),right(右下角横坐标)是相对于它的父容器来说的,右、下为正。
2.View在平移过程中,top和left的值不会改变,变的是translationX和translationY,导致x和y的值发生改变(x=left+translationX,y=top+translationY),这几个值也是相对父容器的。
3.MotionEvent事件中我们能得到x,y坐标,getX/Y拿到的是触摸点相对当前View左上角的坐标,getRawX/getRawY返回的是相对屏幕左上角的坐标。
4.TouchSlop是系统所能识别的最小滑动距离,在作滑动处理的时候可以用这个值做过滤,通过ViewConfiguration.get(Context context).getScaledTouchSlop()方法获取。
5.VelocityTracker用于追踪手指在滑动过程中的速度,通过如下代码获取:
@Override
public boolean onTouchEvent(MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//要获取速度必须先计算速度,这里是1S内滑动的像素点
velocityTracker.computeCurrentVelocity(1000);
float velocityX = velocityTracker.getXVelocity();
float velocityY = velocityTracker.getYVelocity();
//不用的时候回收内存
velocityTracker.clear();
velocityTracker.recycle();
return super.onTouchEvent(event);
}
6.GestureDetector辅助检测手势(单击,双击,滑动,长按等行为)。
GestureDetector detector = new GestureDetector(gestureListener);
detector.setIsLongpressEnabled(false);//解决长按后无法监听的问题
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean b = detector.onTouchEvent(event);
return b;
}
监听事件回调
OnGestureListener:
onDown 轻触屏幕的一瞬间
onShowPress 未松开或拖动的状态
onSingleTapUp 单击
onScroll 拖动
onLongPress 长按
onFling 快速滑动
OnDoubleTapListener:
onSingleTapConfirmed 单击
onDoubleTap 双击
onDoubleTapEvent 双击期间
7.ScrollTo(绝对距离)和ScrollBy(相对距离)只能移动View的内容,View本身是不会移动的,mScrollX的值随移动而变,从右往左为正,反之为负;mScrollY从下往上为正,反之为负。scroll值指的是View内部滑动的值,View初始化的scroll值为0,延View坐标轴正反向滑动,scroll值为负,反之为正。它们用在父布局上就是移动子View,用在View上就是移动View的内容(比如TextView移移动的是文本,ImageView移动的是内部的Drawable对象)
8.View动画平移的只是影像,真身还在原来的位置,绑定的事件依然在原位置才能触发,属性动画在3.0以上可以解决这个问题。View平移后要设置fillafter为true,否则影像会在完成动画的瞬间回到原点。
9.Scroller的典型代码如下:
private void init(){
scroller = new Scroller(context);
}
public void smoothScrollTo(int desX,int desY){
int scrollX = getScrollX();
int scrollY = getScrollY();
int x = desX-scrollX;
int y = desY-scrollY;
//Scroller本身不滑动,这里是设置起点位置、位移距离和滑动事件间隔,在startScroll方法内部,finalX=scrollX+x。
scroller.startScroll(scrollX,scrollY,x,y,1000);
//这里调用View的重绘,在onDraw方法中会去调用computeScroll方法
invalidate();
}
@Override
public void computeScroll() {
//computeScrollOffset方法会根据流逝时间百分比,计算scroller的目标位置,然后调用scrollTo进行滑动,这个方法为true时表示还在滑动,为false时表示滑动完成
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();//下一次重绘
}
}
10.弹性滑动的主要思想都是计算时间百分比来计算滑动位置,然后用scrollto方法来实现滑动。
11.事件的点击传递流程是activity-window-decorview-view,事件点击触发流程如下核心代码所示:
(1)view的分发流程(view其实只能消费事件)
public boolean dispatchTouchEvent(MotionEvent event) {
boolean enable = ENABLED_MASK == ENABLED;
boolean b = false;
if(ListenerInfo!=null && enable ){//ListenerInfo在set各种listener的时候会自动初始化
if(mOnTouchListener!=null){
b = mOnTouchListener.onTouch(view,event);
}else{
b = onTouchEvent(event);
}
}
return b;
}
public boolean onTouchEvent(MotionEvent event) {
//clickable只要CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE任一可用就为true
boolean clickable = CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE;
if(enabled == disabled){
return clickable;
}
//View默认消耗事件(返回true),除非是不可点击的(clickable和longclickable都为false)
if(clickable){
switch(event.getAction()){
case MotionEvent.ACTION_UP:
performClick();
break;
}
return true;
}
return false;
}
public void performClick() {
if (mOnClickListener != null) {
mOnClickListener.onClick(view);
}
}
(2)viewGroup的分发流程:
public boolean dispatchTouchEvent(MotionEvent event) {
final boolean intercepted;
//如果是按下或者mFirstTouchTarget 不为空,会再次判断是否需要拦截事件
if (event.getAction() == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT可以用requestDisallowInterceptTouchEvent()方法改变
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果这个标志为false,再判断onInterceptTouchEvent()方法是否需要拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//没拦截才走下面的流程
if (!canceled && !intercepted) {
//注意这里只有按下的时候才会走,如果子view的ACTION_DOWN都返回false了,那么ACTION_MOVE都不会传递给子view
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
//这里会根据子view的Z轴值来组装子view列表
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
//根据可见性和优先级,找到子view分发事件
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
//dispatchTransformedTouchEvent里面,cancel为false,不会传递ACTION_CANCEL事件,接下来,child不为null,会调用child的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//如果子view消费了事件,跳出循环
//addTouchTarget方法会给mFirstTouchTarget 和newTouchTarget 赋值,他们俩是相同的对象,这个对象的next是null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
//拦截的时候,mFirstTouchTarget 为null,直接调用view的分发流程
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
//ACTION_MOVE和ACTION_UP的分发都是走的这里的逻辑
TouchTarget target = mFirstTouchTarget;
while (target != null) {
//next 肯定为空,循环只会跑一次
final TouchTarget next = target.next;
//这里只有ACTION_DOWN被子view消费了,判断才为真,不会再分发一次
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//如果拦截了,或者 需要通知子view ACTION_CANCEL事件
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
//如果取消了,会重置mFirstTouchTarget,所以前面判断到一旦拦截,这个事件序列就不会再判断是否需要拦截
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
target = next;
}
}
return handled ;
}
12.子View可以通过requestDisallowInterruptTouchEvent方法干预父元素除action_down以外的事件分发过程。
13.viewgroup不是每次都调用onInterceptTouchEvent方法,一旦viewgroup决定拦截,后面所有事件都交给它处理,不会再调用onInterceptTouchEvent判断,只有dispatchTouchEvent方法确保每次都会调用。
14.滑动冲突解决方案只是滑动规则不同而已,解决方案一般是外部拦截法和内部拦截法。伪代码如下:
外部拦截法:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要拦截){//根据不同滑动规则判断
intercept = true;
}else{
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
return intercept;
}
内部拦截法(注意父布局的onInterceptTouchEvent方法除Action_Down返回false外都返true):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要拦截){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
网友评论