View的事件体系
来源:互联网 发布:淘宝如何查看买家评价 编辑:程序博客网 时间:2024/06/07 06:30
View的基础知识
通常来说用户界面都是由Activity组成,Activity中关联了一个PhoneWindow创建,在这个窗口下管理了一个视图树。这颗视图树的顶级视图就是一个GroupView类型的DecorView,DecorView下就是各个ViewGroup,ViewGroup下是各个View。
View的位置由四个顶点决定,分别对应四个属性:top,left,right,bottom,通过这几个单词就很容易知道所代表的意思。但是,这些坐标都是相对于View的父容器来说的,是一种相对坐标。
MotionEvent
当你手指接触屏幕后会产生一些列事件,主要有ACTION_DOWN,ACTION_MOVE,ACTION_UP,所以我们平时所知道的点击事件、滑动事件分别是DOWN->UP,DOWN->MOVE->MOVE….->UP。说到这里就必须要提到一个概念TouchSlop。TouchSlop是系统所能识别出的被认为是滑动的最小距离,当两次滑动之间的距离小于这个常量,系统就不认为你是在进行滑动。关于Android系统中的滑动事件我们另外再讲,这里主要讲自定义View控件和事件分发机制
自定义控件
虽然Android提供了很多强大的UI控件,但是依旧不能满足开发人员的需求。我们只能通过自定义View实现。自定义View也有几种实现类型,分别为继承View,继承现有控件(如ImageView),继承自ViewGroup实现布局类。比较主要的知识点就是测量与布局,View的绘制,触摸事件,动画等。
自定义View————最为自由的一种实现
public class SimpleImageView extends View { private Paint mPaint; private Drawable drawable; private int mWidth; private int mHeight; public SimpleImageView(Context context) { super(context); } public SimpleImageView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initAttributeSet(attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); } public SimpleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public SimpleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } private void initAttributeSet(AttributeSet attrs) { if (attrs != null) { TypedArray array = null; try { array = getContext().obtainStyledAttributes(attrs, R.styleable.SimpleImageView); drawable = array.getDrawable(R.styleable.SimpleImageView_src); measureDrawable(); } finally { if (array != null) { array.recycle(); } } } } private void measureDrawable() { if (drawable == null) { throw new RuntimeException("drawable非空"); } mWidth = drawable.getIntrinsicWidth(); mHeight = drawable.getIntrinsicHeight(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(mWidth, mHeight); } @Override protected void onDraw(Canvas canvas) { if (drawable == null){ return; } canvas.drawBitmap(ImageUtils.drawableToBitmap(drawable),getLeft(),getTop(),mPaint); }}
我们首先创建了一个继承自View的SimpleImageView类,在含有构造函数我们会获取该控件的属性,并且进行初始化画笔和绘制图片。在vaule/attr.xml中我们定义了这个View的属性,便于后续使用,attr.xml文件如下:
<declare-styleable name="SimpleImageView"> <attr name="src" format="integer"/></declare-styleable>
当我们在XML文件中使用该控件时,需要指定它的图片资源。当应用启动时会从这个xml布局中解析SimpleImageView的属性,例如宽、高。进入SimpleImageView的构造函数后会调用initAttrs函数进行初始化。在initAttrs函数中,我们会首先读取SimpleImageView的属性集TypedArray;再从该对象中读取SimpleImageView_src属性值,该属性是一个drawable的资源id,然后我们根据这个id从该TypedArray对象中获取到该id对应的Drawable,最后调用measureDrawable函数测量该图片的大小。
关于该View控件的宽高是这样的。我们在SimpleImageView中设置了两个字段mWidth,mHeight,分别表示该视图的宽高。在measureDrawable函数中,我们通过在xml文件中指定资源id对应的drawable得到图片的宽高,并且把它们作为SimpleImageView的宽高。然后在SimpleImageView被加载的时候,首先会调用onMeasure函数测量SimpleImageView的大小,然后再把图片绘制出来。
关于Canvas 和Paint的函数较多,但理解起来比较简单,我们不多讲。另外关于Scroller的使用我们单独讲。
View的事件分发机制
我们上边讲了View的触摸事件,那么就会遇到View的另一大难题滑动冲突,解决办法的理论基础就是事件分发机制。
在介绍点击事件的传递规则之前,我们要明白这里分析的对象就是MotionEvent,即点击事件。当一个MotionEvent生成以后,系统需要把这个事件传递给一个具体的View,这个传递过程就是分发过程。点击事件的分发过程由三个很重要的方法来完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件分发。如果事件能够传递给当前View,那么此方法一定会调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent的方法影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个时间序列中,当前view无法再次接收到事件。其实,三个函数的关系可以用如下代码表示:
public boolean dispatchTouchEvent(MotionEvent event){ boolean consume = false; if (onInterceptTouchEvent(event)){ consume = onTouchEvent(event); }else { consume = child.dispatchTouchEvent(event); } }
对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true,就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截这个事件,这时当前事件就会传递给它的子元素,如此传递下去,知道事件最终被处理。
当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,onTouchEvent方法将不调用。由此可见,给View设置的OnTouchListener,它的优先级比onTouchEvent要高。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。可见,我们平时常用的OnClickListener,其优先级最低,即处于事件传递的最末端。
这里我们要提一点,事件的传递过程遵循如下顺序:Activity->Window-View.当View的onTouchEvent方法返回false时怎么办呢?这时,它的父容器的onTouchEvent方法被启用,以此类推。直至传递给Activity处理。
这里总结了一些结论,帮助大家理解事件的分发机制。
同一个事件序列是指从手指接触屏幕的那一刻算起,到手指离开屏幕的那一刻结束。也就是这个事件序列以down事件开始,中间含有不定数量的move事件,最终以up结束。
正常情况下,一个事件序列只能被一个View拦截且消耗
- 一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理。但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
- 某个View一旦决定拦截,那么只一个事件序列只能由它处理,并且它的onInterceptTouchEvent不会再被调用。
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那么同一事件序列中的其他事件将不会再交给它处理,并且事件将重新交由它的父元素处理,即父元素的onTouchEvent会被调用。
- 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件将会消失,此时父元素的onTouchEvent并不会调用。
- ViewGroup默认不拦截任何事件
- View没有onInterceptTouvhEvent方法,一旦事件传递给它,那么它的onTouchEvent方法就会被调用。
- View的onTouchEvent默认都会消耗事件,除非它是不可点击(clickable和longClickable同时为false)。View的longClickable属性默认都是false,clickable属性要分情况,比如Button的clickable属性为true,TextView默认为false。
- View的enable属性不影响onTouchEvent的默认返回值。
- onClick会发生的前提是View是可点击的,并接收到了down和up事件。
- 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View
- View的事件体系
- View 的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- spring三种事物处理方式
- HTTP状态码详解
- Android Studio 学习第一章(下载安装)
- 剑指offer 26 复杂链表的复制
- 数据库索引
- View的事件体系
- pageHelper分页插件的使用
- spring boot -+- httpclient访问api -+-下载pdf文档 总结
- Ubuntu上安装RED5服务器
- vivo面试
- leet code 008:atoi 字符串转整型数
- java读取txt文件乱码解决方案
- cocos Creator js 房卡麻将/血战/H5四川麻将源码下载搭建
- mybatis-mysql-like,if,时间大于小于的判断