View事件传递与绘制机制,自定义View实现理解
来源:互联网 发布:网络作家富豪榜2015 编辑:程序博客网 时间:2024/06/05 02:05
.View事件分发机制
1、基础知识
2、传递流程
(1) 事件从 Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的 View(ViewGroup)开始一直往下(子 View)传递。子 View 可以通过 onTouchEvent()对事件进行处理。
(2) 事件由父 View(ViewGroup)传递给子 View,ViewGroup 可以通过 onInterceptTouchEvent()对事件做拦截,停止其往下传递。
(3) 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数,从下往上的消费次序。
(4) 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他事件不会传递过来。
3.关于VIew的onTouchEvent()中事件分发判断逻辑解析:
1.接收到Action_down,
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- break;
- private final class CheckForTap implements Runnable {
- public void run() {
- mPrivateFlags &= ~PREPRESSED;
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick(ViewConfiguration.getTapTimeout());
- }
- }
- }
- class CheckForLongPress implements Runnable {
- private int mOriginalWindowAttachCount;
- public void run() {
- if (isPressed() && (mParent != null)
- && mOriginalWindowAttachCount == mWindowAttachCount) {
- if (performLongClick()) {
- mHasPerformedLongPress = true;
- }
- }
- }
2.接收到Action_move:
- private void removeTapCallback() {
- if (mPendingCheckForTap != null) {
- mPrivateFlags &= ~PREPRESSED;
- removeCallbacks(mPendingCheckForTap);
- }
- }
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
3.接收到Action_up:
a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
c、如果是500ms以后,那么有两种情况:
i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
ii. 没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
二.View绘制流程
1. View 树的绘图流程
ViewRoot.java
类的performTraversals()
函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:2. 概念measure 和 layout
measure(int, int)
方法发起,从上到下有序的测量 View,在 measure 过程的最后,每个视图存储了自己的尺寸大小和测量规格。 layout 过程由layout(int, int, int, int)
方法发起,也是自上而下进行遍历。在该过程中,每个父视图会根据 measure 过程得到的尺寸来摆放自己的子视图。measure 过程传递尺寸的两个类
- ViewGroup.LayoutParams (View 自身的布局参数)
- MeasureSpecs 类(父视图对子视图的测量要求)
ViewGroup.LayoutParams
这个类我们很常见,就是用来指定视图的高度和宽度等参数。对于每个视图的 height 和 width,你有以下选择:- 具体值
- MATCH_PARENT 表示子视图希望和父视图一样大(不包含 padding 值)
- WRAP_CONTENT 表示视图为正好能包裹其内容大小(包含 padding 值)
ViewGroup 的子类有其对应的 ViewGroup.LayoutParams 的子类。比如 RelativeLayout 拥有的 ViewGroup.LayoutParams 的子类 RelativeLayoutParams。
有时我们需要使用 view.getLayoutParams() 方法获取一个视图 LayoutParams,然后进行强转,但由于不知道其具体类型,可能会导致强转错误。其实该方法得到的就是其所在父视图类型的 LayoutParams,比如 View 的父控件为 RelativeLayout,那么得到的 LayoutParams 类型就为 RelativeLayoutParams。MeasureSpecs
测量规格,包含测量要求和尺寸的信息,有三种模式:UNSPECIFIED
父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,EXACTLY
父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)
直接得到子控件的尺寸。- AT_MOST
父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。
3. measure 核心方法
measure(int widthMeasureSpec, int heightMeasureSpec)
该方法定义在View.java
类中,为 final 类型,不可被复写,但 measure 调用链最终会回调 View/ViewGroup 对象的onMeasure()
方法,因此自定义视图时,只需要复写onMeasure()
方法即可。onMeasure(int widthMeasureSpec, int heightMeasureSpec)
该方法就是我们自定义视图中实现测量逻辑的方法,该方法的参数是父视图对子视图的 width 和 height 的测量要求。在我们自身的自定义视图中,要做的就是根据该 widthMeasureSpec 和 heightMeasureSpec 计算视图的 width 和 height,不同的模式处理方式不同。- setMeasuredDimension()
测量阶段终极方法,在onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中调用,将计算得到的尺寸,传递给该方法,测量阶段即结束。该方法也是必须要调用的方法,否则会报异常。在我们在自定义视图的时候,不需要关心系统复杂的 Measure 过程的,只需调用setMeasuredDimension()
设置根据 MeasureSpec 计算得到的尺寸即可,你可以参考 ViewPagerIndicator 的 onMeasure 方法。
4. layout 相关概念及核心方法
首先要明确的是,子视图的具体位置都是相对于父视图而言的。View 的 onLayout 方法为空实现,而 ViewGroup 的 onLayout 为 abstract 的,因此,如果自定义的 View 要继承 ViewGroup 时,必须实现 onLayout 函数。
getMeasuredWidth()
和getMeasuredHeight()
方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight,作为自己的 width 和 height。然后调用每一个子视图的layout(l, t, r, b)
函数,来确定每个子视图在父视图中的位置。5. 绘制流程相关概念及核心方法
先来看下与 draw 过程相关的函数:
View.draw(Canvas canvas): 由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写
onDraw(Canvas)
方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用super.draw(canvas)
完成系统的绘制,然后再进行自定义的绘制。View.onDraw():
View 的onDraw(Canvas)
默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。dispatchDraw() 发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了
dispatchDraw()
来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()
进行复写。
drawChild(canvas, this, drawingTime)
直接调用了 View 的child.draw(canvas, this,drawingTime)
方法,文档中也说明了,除了被ViewGroup.drawChild()
方法外,你不应该在其它任何地方去复写或调用该方法,它属于 ViewGroup。而View.draw(Canvas)
方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(Canvas)
的说明。从参数中可以看到,child.draw(canvas, this, drawingTime)
肯定是处理了和父视图相关的逻辑,但 View 的最终绘制,还是View.draw(Canvas)
方法。invalidate()
请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()
过程,并且只绘制那些调用了invalidate()
方法的 View。- requestLayout()
当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发measure()
和layout()
过程,但不会进行 draw。
6. 自定义View实现方法
(1)自绘控件
- public class CounterView extends View implements OnClickListener {
- private Paint mPaint;
- private Rect mBounds;
- private int mCount;
- public CounterView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBounds = new Rect();
- setOnClickListener(this);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- mPaint.setColor(Color.BLUE);
- canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
- mPaint.setColor(Color.YELLOW);
- mPaint.setTextSize(30);
- String text = String.valueOf(mCount);
- mPaint.getTextBounds(text, 0, text.length(), mBounds);
- float textWidth = mBounds.width();
- float textHeight = mBounds.height();
- canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
- + textHeight / 2, mPaint);
- }
- @Override
- public void onClick(View v) {
- mCount++;
- invalidate();
- }
- }
(2)、组合控件
组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
(3)、继承控件
- View事件传递与绘制机制,自定义View实现理解
- 基础篇-View事件传递与绘制机制,自定义View实现理解
- 深入理解 View 的事件传递机制
- 浅谈Android自定义View事件传递机制
- Android自定义view之事件传递机制
- View事件传递机制
- View 事件传递机制
- View事件传递机制
- android 自定义View(一) View的事件分发与绘制
- Android View 事件传递机制
- Android View事件传递机制
- Android-view事件传递机制
- View的事件传递机制
- 【Android View】 事件传递机制
- View的事件传递机制
- View的事件传递机制
- 浅谈View事件传递机制
- view的事件传递机制
- hello everybody, my name is harmful, i am so excite.
- IT与风投
- Linux服务器安装SVN总结
- 图的最短路
- 【计算几何】【UVA 12304】2D Geometry 110 in 1!
- View事件传递与绘制机制,自定义View实现理解
- 提交方式post
- Android jni开发-3(jni函数详解)
- Facobook倡导的21条高效工作方式
- 微服务之注册服务至EurekaServer
- 超实数真的存在吗?纪念此文发表5周年
- 《自卑与超越》读书笔记
- [vue.js音乐App开发记录]vue2.0通过二级路由实现页面切换
- 保留