Android知识点总结(二)View的事件体系

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chaoyangsun/article/details/81232691

Android开发艺术探索学习笔记

VIEW的基础知识

MotionEvent和TouchSlop

MotionEvent
getX/getY: View相对于父容器的x和y坐标
getRawX/getRawY: 相对于屏幕左上角的x和y坐标

TouchSlop
TouchSlop是系统能识别的滑动的最小距离! 和设备有关,不同设备的值可能不一样,通过ViewConfiguration.get(getContext).getScaledTouchSlop()获取!

VelocityTracker 、 GestureDetector和Scroller

VelocityTracker
VelocityTracker表示速度追踪,在View的onTouchEvent方法中追踪当前点击事件的速度:

VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//获取当前速度:
velocityTracker.computeCurrentVelocity(1000);//设置计算速度的时间间隔
int xVelocity = (int)velocityTracker.getXVelocity();//获取水平速度
int yVelocity = (int)velocityTracker.getYVelocity();//获取垂直速度

最后,不使用时,调用clear功能回收:

velocityTracker.clear();
velocityTracker.recycle();

GestureDetector
手势检测,用于辅助检测用户的单机、滑动、长按、双击等行为!

方法名 描述 所属接口
onDown 手指轻轻触摸屏幕的一瞬间,由1个ACTION_DOWN触发 OnGestureListener
onShowPress 手指轻轻触摸屏幕,尚未松开或拖动,由1个ACTION_DOWN触发 注意和onDown的区别,它强调的是没有松开或者拖动的状态 OnGestureListener
onSingleTapUp 手指(轻轻触摸屏幕后)松开,伴随着1个MotionEvent.ACTION_UP而触发,这是单击行为 OnGestureListener
onScroll 拖动,由一个ACTION_DOWN,多个ACTION_MOVE触发 OnGestureListener
onLongPress 用户长久地按着屏幕不放,即长按 OnGestureListener
onFling 用户按下触摸屏、快速滑动后松开,由1个ACTION_DOWN、多个ACTION_MOVE和1个ACTION_UP触发,这是快速滑动行为 OnGestureListener
onDoubleTap 双击,由个连续的单击组成,和onSingleTapConfirmed共存 onDoubleTapListener
onSingleTapConfirmed 严格的单击行为 它和onSingleTapUp不同,在双击事件中会触发后者 onDoubleTapListener
onDoubleTapEvent 表示发生了双击行为,双击期间ACTION_DOWN、ACTION_MOVE和ACTION_UP都会触发 onDoubleTapListener

Scroller
弹性滑动对象,用于实现View的弹性滑动。一般和computeScroll配合使用:

Scroller scroller = new Scroller(mContext);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX, int destY){
    int scrollX = getScrollX();
    int delta = destX - destY;
    //1000ms内滑向destX, 效果就是慢慢滑动
    mScroller.startScroll(scrollX, 0, delta, 0, 1000);
    invalidate();
}
@Verride
public void computeScroll(){
    if(mScroller.computeScrollOffset()){
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postINVALIDATE();
    }
}

VIEW的滑动

scrollTo/scrollBy

/**
 * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to  x代表view到达的位置
 * @param y the y position to scroll to
 */
public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

/**
 * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally x代表view横向滑动的距离
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

这两种方法移动的是内容,view本身的位置,并没有变!如一个TextView移动的只是其文字,点击位置,背景色所在位置均不变!
这里写图片描述

动画

动画也可以实现view的滑动,除属性动画外,其他动画并不能实现view的位置参数,只是其内容的移动包括背景色的位置!
属性动画可以真正改变view的位置参数:

ObjectAnimator.ofFloat(textView, "translationX", 0, 100).setDuration(500).start();

改变布局参数

改变LayoutParams也可以实现view的滑动,这种移动可以真正改变view的位置信息!

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
    layoutParams.height += 50;
    layoutParams.topMargin += 100;
    textView.setLayoutParams(layoutParams);

下面是一个实现跟手滑动的例子:

扫描二维码关注公众号,回复: 3358280 查看本文章
private android.widget.ImageView iv1;
private int mLastX;
private int mLastY;
...

iv1.setOnTouchListener(this);
...

@Override
public boolean onTouch(View v, MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            int translationX = (int) (v.getTranslationX() + deltaX);
            int translationY = (int) (v.getTranslationY() + deltaY);
            v.setTranslationX(translationX);
            v.setTranslationY(translationY);
            break;
        case MotionEvent.ACTION_UP:

            break;
        default:
            break;
    }
    mLastX = x;
    mLastY = y;
    return true;
}

View的弹性滑动

可以使用Scroller、动画、延时等方法实现:

//属性动画
ObjectAnimator.ofFloat(tv1, "translationX", 0, 100).setDuration(500).start();

 //下面是模仿Scroller实现弹性滑动:动画实质上没有作用再任何对象,只是提供一个过
 //程,真正的实现是自己利用动画的时间百分比计算距离,最终使用scrollTo实现!
final int startX = 0;
final int deltaX = 500;
final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float fraction = valueAnimator.getAnimatedFraction();
        Log.i("ss", "onAnimationUpdate: "+ fraction);
        tv2.scrollTo(startX+(int) (deltaX*fraction), 0);
    }
});
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();              

View的事件分发机制

事件分发的过程有三个方法共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent

dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发,如果事件能够传递到View,那么此方法一定会被调用!返回结果受当前View的onTouchEvent和子view的dispatchTouchEvent方法影响!

onInterceptTouchEvent(MotionEvent event)
判断是否拦截某个事件,如果拦截了某个事件,那么在同一事件序列中,此方法不会再次调用!

onTouchEvent(MotionEvent ev)

改方法在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消费此事件,如果不消费,同一事件序列中,当前view无法再次接收到事件!

他们的关系可以用以下的伪代码来表示:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean cosume = false;
    if(onInterceptTouchEvent(ev)){//如果拦截了 由当前view的onTouchEvent决定
        consume = onTouchEvent(ev);
    }else{//如果没有拦截,交由子组件处理,然后再去子组件重复判断
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

整体来说一个view 内部的事件调用,大致有一下流程:

dispatchTouchEvent => onInterceptTouchEvent => onTouch => onTouchEvent => onClick

首先是调用dispatchTouchEvent分发,然后判断其onInterceptTouchEvent,若返回true,则事件由当前view处理,这时如果设置了OnTouchListener,
则onTouch会被调用,否则调用onTouchEvent,也就说都提供的话,onTouch会屏蔽onTouchEvent。在onTouchEvent中,如果设置了onClickListener,
则onClick会被调用。而如果当前view不拦截事件,则继续调用子view的dispatchTouchEvent...

View的滑动冲突

常见的滑动冲突场景有以下三种
这里写图片描述
场景一处理冲突的规则:
左右滑动时,让外部view拦截点击事件;上下滑动时,让内部view拦截点击事件!
另外两种,要根据实际情况、业务逻辑具体解决!
外部拦截法:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        float x = ev.getX();
        float y = ev.getY();
        switch (ev.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;
        }
        mLastY = y;
        mLastX = x;
        return intercepted;
    }

内部拦截法:

//子view
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要此类点击事件){
                    parent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.i("TAG", "ACTION_UP:inner ---------------");
                break;
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

//父view
public boolean onInterceptTouchEvent(MotionEvent event){
    int action = event.getAction();
    if(action == MotionEvent.ACTION_DOWN){
        return false;
    }else{
        return true;
    }
}

猜你喜欢

转载自blog.csdn.net/chaoyangsun/article/details/81232691