好久好久没更新了,不知道大家还有没有在看以前的一些博文,这段时间换了个坑位还是有点小忙呢!鉴于最近工作接触自定义View
,Canvas
比较多,所以打算开个系列,详细讲讲View
的那些事。
View是什么
要学习自定义View
,我们首先应该清楚的认识到View
是个什么东西。那么View究竟是什么呢?
-
从代码层面看,
View
是一个类,是所有控件的直接或间接父类,详情见图-View子孙关系,图中只显示了部分View的子孙,有兴趣的朋友可以尝试自己去画下这幅图 -
从用户角度看,
View
是用户界面的组成元素,View
参与用户交互
那么View
究竟长什么样呢?见图-View界面边界
从上图可以看出(开发者选项->打开布局边界),界面上框出了很多大小不一的矩形框,我们可以把这里的每一个框都看作一个View
或一个View
子孙类,在Android Studio中打开布局管理器,也能看到类似于这种效果,如图-Android Studio View界面边界。
通过上面描述,我们可以清晰的认识到,View
对应着界面上的一块矩形区域,在这块矩形区域内可以进行用户交互,那么这个矩形区域内部各种各样的颜色,Icon又是怎么显示出来的呢?
View生命周期
我们都知道Android
中大多数控件具有生命周期敏感性,那么View有没有生命周期呢?答案是肯定的,View
作为用户交互的重要组成元素,与Activity
一样具有显式生命周期,只不过一般我们不去过分强调而已。
如图-View生命周期所示,其中描述了View
的整个生命周期过程,其中我们需要关注r如下几点:
-
invalidate()
的同作用函数postInvalidate()
,两者均用于更新View
内容,只不过postInvalidate()
发生在单独的线程,invalidate()
发生在主线程; -
View
整个生命周期中有四个重要函数,构造器从XML文件中解析View
属性,onMeasure()
测量View
大小,测量过程与测量模式,Padding
,Margin
等有关,onLayout()
完成View
的位置布局,onDraw()
完成View
内容的绘制; -
如上图所示,如果需要
View
重新计算大小,则需要调用requestLayout()
,启动View
的深度优先遍历过程,重新构造View
树,关于View
树的相关信息,我们会在后续文章中描述; -
上图中并没有绘制
View
触屏事件相关的响应函数,会在随后的View
事件处理部分进行详细描述;
View坐标系
不知道大家是否还记得我们学习绘制图形之前,最开始学习的是什么?相信大多数朋友都知道那就是坐标系,对于在View
上绘制图形也是一样,首先需要要绘制图形在View
内的位置,随后才能使用画笔进行绘制,那么View
内部坐标系是怎样的呢?
如图-View坐标系所示,View
内部的坐标原点位于View
所在矩形的左上角,以屏幕水平右方向为X轴正向,以垂直向下方向为Y轴正向,那么我们经常使用的View#getTop()
,View#getLeft()
,View#getBottom()
,View#getRight()
等返回的又是哪里的距离呢?
具体的函数值说明,如图-View函数值说明所示,大家自行参考。
View事件处理
View
作为参与用户交互的重要元素之一,响应用户操作必不可少,View
内部的事件分发机制如图-View内部分发流程。
从图-View内部分发机制 ,在View
内部事件分发过程中,事件起始于父控件调用dispatchTouchEvent
,止于onTouchListener
或者onTouchEvent
返回true(true-事件被消耗,false-未消耗事件),如果onTouchListener
返回false或为空,事件会进一步传递到onTouchEvent
处理,如果onTouchEvent
返回false,事件会被扔回父控件处理,如果每级都按照上述流程返回false,则事件会被上传到操作系统抛弃掉。
现有状况下我们一般有两种方式处理用户事件:
-
重写
View#onTouchEvent()
-
View onTouchListener
接口
那么这两种处理方式有什么区别呢?从下面源码中我们可以看出onTouchListener
的优先与onTouchEvent
,所以在重写onTouchEvent
无效的情况下,除了check 父控件是否向下分发事件以外,还需要check该控件是否有onTouchListener
的监听。
/** Android P 源码代码片段 **/
public boolean dispatchTouchEvent(MotionEvent event) {
....
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//优先响应onTouchListener
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//onTouchListener为空或者返回false时,响应onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
更多详情请关注后续更新,觉得不错的朋友记得动动手指转发哦!