自定义View详解(1)

254 阅读4分钟

好久好久没更新了,不知道大家还有没有在看以前的一些博文,这段时间换了个坑位还是有点小忙呢!鉴于最近工作接触自定义ViewCanvas比较多,所以打算开个系列,详细讲讲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大小,测量过程与测量模式,PaddingMargin等有关, 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;    }

更多详情请关注后续更新,觉得不错的朋友记得动动手指转发哦!