自定义View详解(1)

好久好久没更新了,不知道大家还有没有在看以前的一些博文,这段时间换了个坑位还是有点小忙呢!鉴于最近工作接触自定义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;
    }

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

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值