开发艺术探索--Android的View事件体系

本章包括: Android的View体系,View的滑动,View的事件及滑动冲突

第三章,View事件体系

View基础
View滑动
弹性滑动
事件分发
滑动冲突

View基础

View参数

View的四个基本属性

left = getLeft();
top = getTop();
bottom = getbottom();
right = getRight();

其中参数都是相对于父容器的.

View位置参数

从Android3.0增加了几个参数:x、y、translationX、translationY,
其中x和y是view左上角的坐标,而translationX和translationY是view左上角相对于父容器的偏移量,
这几个参数也是相对于父容器的坐标,并分别提供了get\set方法,换算关系如下:

x = translationX + left; 
y = translationY + top; 

需要注意的是left和top表示的是原始左上角的位置信息,其值并不会改变
view平移时发生改变的是x、y、translationX、translationY。
所以如果你的view位置不动的话,getX和getLeft的值是一样的。

width = right - left; 
height = bottom - top;
MotionEvent&TouchSlop

MotionEvent主要包含了一系列事件

其中,getX/getY返回的是相对于当前view左上角的x和y坐标,getRawX/getRawY返回的是相对手机屏幕左上角的x和y坐标。

TouchSlop 被认为是系统所能识别的最小滑动距离,和设备有关,不同设备上的值可能不同.

 ViewConfiguration.get(getContext()) .getScaledTouchSlop();
VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度. 左 -> 右:速度为正值

// 在onTouchEvent()方法中追踪当前点击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//获取滑动速度
//计算速度,设置时间单元,单位毫秒
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
//速度 = (终点位置 - 起始位置) / 时间段
//不需要的时候回收内存
velocityTracker.clear();
velocityTracker.recycle(); 
GestureDetector

手势检测,检测用户的单击、滑动、长按、双击等行为.

//创建GestureDetector对象并实现OnGestureListener接口,建议使用V4包中的GestureDetectorCompat兼容包
mGestureDetectorCompat =
        new GestureDetectorCompat(this, new GestureDetector.SimpleOnGestureListener());
//SimpleOnGestureListener包含了一系列手势事件的默认实现
@Override public boolean onTouchEvent(MotionEvent event) {
    boolean b = mGestureDetectorCompat.onTouchEvent(event);
    return b;
}

建议: 监听滑动相关,自己处理
监听双击这种行为,使用GestureDetector处理

Scroller

弹性滑动对象,用于实现View的弹性滑动,需要和View的computeScroll方法配合使用.

private Scroller mScroller = new Scroller(mContext);

/**
 * 缓慢移动到指定位置
 * @param destX 指定位置x坐标
 * @param destY 指定位置y坐标
 */
private void smoothScrollTo(int destX, int destY) {
    int scrollerX = getScrollX();
    int deltaX = destX - scrollerX;
    // 1000ms内滑向destX
    mScroller.startScroll(scrollerX,0,deltaX,0,1000);
    invalidate();
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

其实还有一个叫做OverScroller的增强工具类,增加了过渡滑动效果.

View的滑动

scrollTo/scrollBy : 左->右:mScrollX为负值,上->下: mScollY为负值
动画: nineoldanimations在3.0以下的本质依然是使用view动画实现.
LayoutParams: 适用于有交互的View

群英传将其分为七种,之前的总结 Android移动View的几种方式

弹性滑动

  1. scrollTo/scrollBy 和computeScroll配合使用
  2. 动画,本身具备弹性滑动效果
  3. 延时策略,通过Handler,postDelayed来实现弹性效果.

事件分发

主要涉及到三个方法,之前的总结Android 6.0事件分发机制源码解析

伪代码说明一切:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
    return cosume;
}
View.onTouchListener#onTouchEvent
    |false -> View.onTouchEvent -> View.onClickListener#onClick
  • View.onTouchEvent 返回false,则parent的onTouchEvent将会被调用:false抛给上级
  • 一个事件序列只能被一个View拦截且消耗,也可以通过特殊手段传递(一锤子买卖)
  • 事件一旦交给一个view处理,则之后不会继续调用这个ViewonInterceptTouchEvent
  • View一旦开始处理事件,如果不消耗ACTION_DOWN,则后续事件也不会再交给它处理。
  • 如果View不消耗ACTION_DOWN以外的其他事件,这个点击事件会消失.
  • ViewGroup默认不处理事件。
  • View#onTouchEvent默认会消耗事件,除非clickable == false && longClickable == false
  • View的enable不影响onTouchEvent的默认返回值
  • onClick会发生的前提是View是可点击的,并且接受了downup事件
  • 事件由外向内传递,通过requestDisallowInterceptTouchEvent可以干预父元素的事件分发

View 滑动冲突

外部和内部滑动方向一致
外部和内部滑动方向不一致
两种都有的嵌套情况

通用处理办法分为: 外部拦截法内部拦截法

  1. 外部拦截法: 父容器需要此事件就拦截,否则不拦截,需要重写父容器的onInterceptTouchEvent
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            intercepted = false;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (父容器需要此类点击事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
        default:
            break;
        }
        return intercepted;
    }
  1. 内部拦截法: 子元素需要事件就直接消耗掉,否则交给父容器处理.需要重写子元素的dispatchTouchEvent并配合requestDisallowInterceptTouchEvent处理.
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (父容器需要此类点击事件) {
                mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            break;
        }
        default:
            break;
        }
        return super.dispatchTouchEvent(event);
    }

推荐采用外部拦截法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值