安卓view基本原理及绘制

1、view理解

view是屏幕上的一块可视区域,它负责用来显示一个区域,并且响应这个区域内的事件。可以说,手机屏幕上的任何一部分可以看得见的地方都是View,很常见,如TextView、ImageView、Button以及Linearlayout、RelativeLayout都是继承于view的。

对于activity来说,我们通过setContentView(view)添加的布局到activity上,实际上都是添加到了activity内部的decorView上面,这个Deorview其实就是一个FrameLayout,因此实际上,我们的布局实际上添加到了frameLayout里面。

2、view工作流程

可分为两部分:
第一部分 显示在屏幕上的部分
第二部分 响应屏幕上的触摸事件的过程

对于现实中屏幕上的过程:是View从无到有,经过测量大小(Measure)、确定在屏幕中的位置(Layout)、以及最终**绘制在屏幕上(Draw)**这一些列的过程。

对于响应屏幕上的触摸事件的过程:则是Touch事件的分发过程(这一部分,在这里先不涉及)。

Measure()、Layout()方法是final修饰的,无法重写,Drawe()虽然不是final的,但是也不建议重写该方法。

3、如何自定义View?
view提供了1、onMeasur() 。 2、onLayout()。3、onDraw() 这样的方法,所谓的自定义view也就是对这三个方法重写的过程。

所谓的自定义view实际上就是仿照View的工作流程,去自己实现的过程。根据继承对象的不同自定义view又分为继承view与viewGroup两种。

1、measure()
view的onMeasure方法如下:

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

内部调用了setMeasuredDimension方法,设置测量的view的宽/高,传递的参数就是宽度和高度。

  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

onMeasure通过父view传递过来的大小和模式,以及自身的背景图片的大小得出自身最终的大小,通过setMeasuredDimension()方法设置给measuredWidth和measuredHeight

普通view的onMeasure逻辑大同小异,基本都是测量自身内容和背景,然后根据父view传递过来的MeasureSpec进行最终的大小和判定,例如TextView会根据文字的长度、文字的大小、文字的行高、文字的行宽、显示方式,背景图片以及父view传递过来的模式和大小最终确定自身的大小

ViewGroup本身没有实现onMeasure,但是他的子类都有各自的实现,通常他们都是通过measureChildWithMargins函数或者其他类似measureChild的函数来遍历测量子view,被GONE的字View将不参与测量,当所有的子View都测量完毕后,才根据父View传递过来的模式和大小来最终决定自身的大小。

ViewGroup一般都在测量完所有子View后才会调用setMeasureDimension()设置自身大小。

这里我们要介绍一下Android中自己定义的测量规格:MeasureSpec。
因为测量过程中,系统会把我们的代码或者布局中的view的LayoutParams转换成MeasureSpec,然后通过MeasureSpec来确定view的宽高。

MeasureSpec由32位int值组成,高2位表示的是测量模式(specMode),后面的30位代表的是测量大小(specSize)

MeasureSpec是由父布局与View自身的LayoutParams来决定的。
MeasureSpec判定规则
经过measure完成后,我们就可以通过getMeasureWidth/Height获取View的宽高。

2、layout()
Layout()方法如果是ViewGroup,则循环遍历所有子View,普通View则空实现,因此如果我们继承ViewGroup我们需要遍历执行所有的child.layout()方法。

Layout方法中接受四个参数,是由父View提供的,指定类子view在父view中的左、上、右、下的位置。父view在指定子view的位置时通常会根据子view在measure中测量的大小来决定。

子view的位置通常还受有其他属性左右,例如父view的orientation、gravity,自身的margin等等,影响布局的因素非常多。

onLayout是ViewGroup用来决定子view摆放位置的,各种布局的差异都在该方法中得到了体现。

onLayout比layout多一个参数,changed,该参数是在setFrame通过对比上次的位置得出是否发生了变化,通常该参数没有被使用的意义,因为父view位置和大小不变,并不能代表子view的位置和大小没有发生改变。
layout布局流程

3、drawer()

drawer是绘制到屏幕上,drawer的工作流程如下
Drawer traversal performs several drawing steps which must be execute。

in the appropriate order:
1、Draw the background (绘制背景)
2、If necessary,save the cavas‘s layer to prepare for fading (保存画布的图层来准备变色)
3、Draw view’s content (绘制内容)
4、Draw children (绘制children)
5、If necessary,draw the fading edges and restore layers (画出褪色的边缘和恢复层)
6、Draw decorations(scrollbars for instance) (绘制装饰比如scollbar)

其中,5不是必须的步骤

在TextView中在该方法中绘制了文字、光标和CompoundDrawable。ImageView中相对简单,只是绘制了图片。

View的绘制主要通过dispatcheDraw。先根据自身的padding剪裁画布,所有的字view都将在画布剪裁后的区域绘制。

遍历所有子view,调用子view的computeScroll对子view的滚动值进行计算。

根据滚动值和子view在父view中的坐标进行画布原点坐标的移动,根据子在父view中的坐标计算出子view的视图大小,然后对画布进行剪裁,请看下面的示意图。

dispatchDraw的逻辑其实比较复杂,但是幸运的是对子view流程都采用该方式,而viewgroup已经处理好了,我们不必要重载该方法对子view进行绘制事件的派遣分发。
drawer绘制流程
几个重要方法:
requestLayout:
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法**要求parent view(父类的视图)重新调用他的onMeasure onLayout来重新设置自己位置。**特别是当view的layoutParamter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法。

postInvalidate与invalidate
界面刷新onDraw方法会执行,区别就是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。鉴于此,如果要使用invalidate的刷新,那我们就得配合handler的使用,如果要在非ui线程中直接使用就调用postInValidate方法即可,这样就省去使用handler的烦恼。

由于时间原因,本文只分析view的绘制上的原理,view的触摸事件后期会讲到。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

约翰兰博之西安分博

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值