• View的事件体系


    View虽然不属于四大组件,但它的作用堪比四大组件,甚至比Receiver和Provider的重要性都大,在Android开发中,Activity承担这可视化的功能,同时Android系统提供了很多基础控件,常见的有Button、Textview、CheckBox等。

    View基础知识

    什么是View

    View是一种界面层的控件的一种抽象,它代表了一个控件。除了View,还有ViewGroup,ViewGroup内部包含了许多个控件,即一组View,在ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系形成了View树的结构。

    View结构图

    View的位置参数

    View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top、left、right、bottom,其中top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角的纵坐标。需要注意的是,这些坐标都是相对于View的父容器来说的,因为它是一种相对坐标。

    View坐标轴

    在Android中,X轴和Y轴的正方向分别为右和下。那么就可以得出以下关系:

    width = right - left
    height = bottom - top
    

    MotionEvent和TouchSlop

    在手指接触屏幕后会产生一系列的事件,典型的事件类型有如下几种:

    • ACTION_DOWN——手指刚接触屏幕
    • ACTION_MOVE——手指在屏幕上移动
    • ACTION_UP——手指从屏幕上松开的一瞬间

    正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,如下:

    • 点击屏幕后离开松开,事件序列为DOWN->UP
    • 点击屏幕滑动一会再松开,事件序列为DOWN->MOVE->.......->MOVE->UP

    同时我们可以通过MotionEvent对象我们可以得到点击事件发生的X坐标和Y坐标。为此,系统提供了两组方法:getX/getY和getRawX/getRawY,区别很简单,前者返回的是相对于当前View左上角的X和Y坐标,而后者返回的相对于手机屏幕左上角的X和Y坐标。

    TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作,因为滑动的距离太短了,系统就不认为它是滑动,这是一个常量,跟设备有关。可以通过以下方式获取:

    ViewConfiguration.get(getContext()).getScaledTouchSlop()
    

    View的滑动

    在Android设备中,滑动几乎是应用的标配,不管是下拉刷新还是SlidingMenu,它们的基础都是滑动。因此,掌握滑动的方法是实现绚丽的自定义控件的基础,有三种方式实现View的滑动:

    • scrollTo/scrollBy方法来实现
    • 使用动画
    • 改变布局参数

    scrollTo/scrollBy方法来实现

    首先,我们需要获取View里的两个属性mScrollX和mScrollY,在滑动过程中,mScrollX的值总是等于View的左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总等于View上边缘和View内容上边缘在竖直方向的距离。View边缘是指View的位置,由四个顶点组成,而View内容边缘是指View中的内容的边缘,scrollTo/scrollBy只能改变View内容的位置而不能改变View在布局中的位置。

    如果从左往右滑动,那么mScrollX为负值,反之为正值,如果从上往下滑动,那么mScrollY为负值,反之为正值。

    View滑动

    使用动画

    使用动画我们能够让一个View进行平移,而平移就是一个滑动。比如:

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

    但是动画移动会有个问题,那就是View动画并不能真正改变View的位置,这会带来一个很严重的问题,就是移动到新位置了,却发现无法触发事件,而单击原来的位置却能触发。那么从Android3.0开始,使用属性动画可以解决上面问题。

    改变布局参数

    改变布局参数,即改变LayoutParams,这个比较好理解,比如我们想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可。

    MarginLayoutParams params = (MarginLayoutParams)mButton1.getLayoutParams;
    params.width += 100;
    parms.leftMargin += 100;
    mButton1.requestLayout();
    

    三种滑动方式对比:

    • scrollTo/scrollBy:操作简单,适合对View内容的滑动
    • 动画:操作简单,主要使用于没有交互的View和实现复杂的动画效果
    • 改变布局参数:操作稍微复杂,适用于有交互的View

    弹性滑动

    知道了View的滑动,我们还需要知道如何实现View的弹性滑动,比较生硬的滑过去,这种方式的用户体验实在太差了,因此我们要实现渐近式滑动。如何实现弹性滑动呢,有个共同思想:将一次大的滑动分成若干次小的滑动,并在一个时间段内完成,常见的弹性滑动具体实现方式有很多,比如通过Scroller、Handler#postDelayed,以及Thread#Sleep等等。

    View的事件分发机制

    点击事件的传递规则

    点击事件的分发过程由三个很重要的方法共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

    • dispatchTouchEvent(MotionEvent ev),用来进行事件的分发。
    • onInterceptTouchEvent(MotionEvent event),用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
    • onTouchEvent(MotionEvent event),在dispatchTouchEvent方法调用,用来处理当前点击事件,返回结果表示是否消耗当前事件。如果不消耗,那么在同一个事件序列中,当前View无法再次接收到事件。
    public boolean dispatchTouchEvent(MotionEvent ev){
      boolean consume = false;
      if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
      }
      else{
        consume = child.dispatchTouchEvent(ev);
      }
      return consume;
    }
    

    简单来说,对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。

    还有一个当View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调,OnTouchListener优先于onTouchEvent。在onTouchEvent中,如果设置了OnClickListener,那么它的OnClick方法会被调用,可以看出我们平时常用的OnClickListener优先级最低,onTouch>onClick.

    View不处理流程:

    View不处理流程

    View处理流程

    View处理流程

    一些总结:

    • 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束。一般是以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
    • 正常情况下,一个事件序列只能被一个View拦截且消耗。
    • 某个View一旦决定拦截,那么这个事件序列就只能由它来处理,那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent会被调用。
    • 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件就会消失,此时父元素的onTouchEvent并不会被调用,最终会交给Activity处理。
    • ViewGroup默认不拦截任何事件。
    • View中没有onInterceptTouchEvent方法。
    • View的onTouchEvent默认都会被消耗,除非它是不可点击的。
    • 事件传递过程是由外向内的,即事件先是传递给父元素,然后再由父元素分发给子View。

    View的滑动冲突

    常见的滑动冲突场景

    滑动冲突三种场景

    场景1:主要是讲ViewPager和Fragment配合使用组成的页面滑动效果,会产生的问题。

    场景2:在开发中,内外两层同时能上下滑动或者内外两层同时能左右滑动。

    场景3:是场景1和场景2两种情况的嵌套。

    如何处理

    根据滑动是水平滑动还是竖直滑动来判断到底是由谁来拦截事件。几种处理方式:

    • 外部拦截法。点击事情都是先经过父容器的拦截处理,如果父容器需要次事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。

    • 内部拦截法。父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗点,否则就交由父容器进行处理。

    阅读扩展

    源于对掌握的Android开发基础点进行整理,罗列下已经总结的文章,从中可以看到技术积累的过程。
    1,Android系统简介
    2,ProGuard代码混淆
    3,讲讲Handler+Looper+MessageQueue关系
    4,Android图片加载库理解
    5,谈谈Android运行时权限理解
    6,EventBus初理解
    7,Android 常见工具类
    8,对于Fragment的一些理解
    9,Android 四大组件之 " Activity "
    10,Android 四大组件之" Service "
    11,Android 四大组件之“ BroadcastReceiver "
    12,Android 四大组件之" ContentProvider "
    13,讲讲 Android 事件拦截机制
    14,Android 动画的理解
    15,Android 生命周期和启动模式
    16,Android IPC 机制
    17,View 的事件体系
    18,View 的工作原理
    19,理解 Window 和 WindowManager
    20,Activity 启动过程分析
    21,Service 启动过程分析
    22,Android 性能优化
    23,Android 消息机制
    24,Android Bitmap相关
    25,Android 线程和线程池
    26,Android 中的 Drawable 和动画
    27,RecylerView 中的装饰者模式
    28,Android 触摸事件机制
    29,Android 事件机制应用
    30,Cordova 框架的一些理解
    31,有关 Android 插件化思考
    32,开发人员必备技能——单元测试

  • 相关阅读:
    -bash: fork: Cannot allocate memory 问题的处理
    Docker top 命令
    docker常见问题修复方法
    The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
    What's the difference between encoding and charset?
    hexcode of é î Latin-1 Supplement
    炉石Advanced rulebook
    炉石bug反馈
    Sidecar pattern
    SQL JOIN
  • 原文地址:https://www.cnblogs.com/cr330326/p/6340970.html
Copyright © 2020-2023  润新知