MicroKibaco / CrazyDailyQuestion

每日一问: 水滴石穿,聚沙成塔,坚持数月, 必有收获~
35 stars 1 forks source link

2019-08-21: 说一说自定义View的流程,以及对view的绘制流程的理解? #17

Open zhjlong opened 5 years ago

zhjlong commented 5 years ago

关于View绘制的流程如下: 1.ViewRoot: ViewRoot连接windowManager和DecorView的纽带,view的三大绘制流程都是通过viewRoot来完成的 2.viewRoot的performTraversals: View的绘制流程是从ViewRoot的performTraversals开始的,依次调用performMeasure , performLayout和performDraw三个方法,分别完成View的测量,布局和绘制三大流程 3.measure: measure完成对自身的测量,接着调用onMeasure方法,对所有的子元素进行测量。这时候measure的流程就从父容器传到子容器中,完成依次测量。接着子元素重复父容器的measure过程,一层一层直到完成整个View树的测量 4.layout: layout方法用于确定自身在父容器的位置,它先判断视图是否发生变化,再调用setFrame确定四个点,这样就确定了View本身的位置。接着调用onLayout,确定所有子元素的位置。这样layout流程就从父容器传到子容器,完成依次layout。 接着子元素重复父容器的layout过程,一层一层直到完成整个View树的layout。 5.draw: 绘制背景 > 绘制自身内容 > 绘制子元素 > 绘制装饰

MicroKibaco commented 5 years ago

  掌握自定义 View 需要掌握三个流程,绘制,布局触摸反馈,由于时间关系,今天我先讲解一下布局过程

布局过程

1.概念:

  用代码的方式计算所有控件的位置和尺寸,这个过程就是布局过程,具体来说计算出每一个View的尺寸和相对父View的相对位置.

2.目的:

  1. 布局过于复杂,导致在书写布局非常深,代码性能比较低,维护性差
  2. xml 无法实现我们想要的效果

3.流程:

a.整体来看:

  1. 测量流程: 从根 View 递归调用到每一级子Viewmeasure() 方法,对它们进行测量
  2. 布局流程: 从根 View 递归调用到每一个级子Viewlayout() 方法
  3. 为什么要分两个流程?

b.个体来看:

  1. 运行前,开发者在 xml 文件里写入对 View 的布局要求 layout_xxx
  2. View 在自己的 onMeasure() 中,根据开发者在 xml 中写的对子 View的要求,和自己可用空间,得出对子View具体尺寸的要求
  3. View 在自己的 onMeasure() 中,根据自己的特性计算出自己的期望尺寸.
    • 如果是 ViewGroup ,还会在这里调用每个子ViewonMeasure() 进行测量
  4. View在子View计算出期望尺寸后,得出子View的实际尺寸和位置保存.
  5. View 在自己的 layout() 方法中,将父 View传进来自己的实际尺寸和位置保存.
    • 如果是 ViewGroup,还会在layout() 方法里调用每个字 Viewlayout()把他们尺寸位置传给他们.

4.具体开发:

5.完全自定义尺寸:

6.自定义Layout:

参考资料

  听我扯这么多其实对你一点帮助也没有,而且还很累,其实真正掌握自定义View还不如参考朱凯老师的文章,而且我建议是细读,基础好点的至少也得花个两三周研究这个专题,看懂和会写是两回事,所以不要看这些没用的,也不要记忆任何概念,网上的博客也不一定靠谱,要彻底忘记我写的东西,如果你靠自己能实现一个仪表盘或者折线图什么的,比看我这东西强一百倍

  1. HenCoder 自定义View-布局
skylarliuu commented 5 years ago

自定义View分为三个流程:测量、布局和绘制。

一个完整的绘制过程会依次绘制以下几个内容:

draw()是总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。

/ View.java 的 draw() 方法的简化版大致结构(是大致结构,不是源码哦):

public void draw(Canvas canvas) {  
    ...

    drawBackground(Canvas); // 绘制背景(不能重写)
    onDraw(Canvas); // 绘制主体
    dispatchDraw(Canvas); // 绘制子 View
    onDrawForeground(Canvas); // 绘制滑动相关和前景

    ...
}
zhengjunke commented 5 years ago

绘制过程分为三部分:Measure - Layout - Draw 一、Measure 过程 1、测量过程由上至下,在measure过程的最后,每个视图将存储自己的尺寸大小和测量规格。 2、measure过程会为一个View及其所有子节点的mMeasureWidth和mMeasuredHeight变量赋值, 该值可以通过getMeasuredWidth和getMeasuredHeight方法获得。 3、measure过程的核心方法: measure() - onMeasure() - setMeasuredDimension(). setMeasuredDimension 是测量阶段的终极方法,在onMeasure()方法中调用,将计算得到的尺寸,传递给该方法,测量阶段结束。在自定义视图时,不需要关心系统复杂的Measure过程,只需要调用setMeasuredDimension()设置根据MeasureSpec计算得到的尺寸即可。同时, onMeasure()方法也必须调用setMeasuredDimension()方法来设置重新测量之后的。 二、Layout 过程 1、子视图的具体位置都是相对于父视图而言的。View的onLayout()方法为空实现,而ViewGroup的onLayout为abstract,因此,自定义的View要继承ViewGroup时,必须实现onLayout函数。 2、在Layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到measure过程得到mMeasuredWidth和mMeasuredHeight,作为自己的width和height。然后调用每一个子视图的layout(),来确定每个子视图在父视图中的位置。 三、Draw 过程 1、所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。 2、onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。 3、dispatchDraw()发起对子视图的绘制,在View中默认为空实现,ViewGroup复写了dispatchDraw()来对其子视图进行绘制。自定义的ViewGroup不应该对dispatchDraw()进行复写。

gys0000 commented 5 years ago

自定义控件,大致需要测量,布局,绘制这三大步。

不过一个完整的控件在完成的过程中往往伴随着动画,触摸事件,滑动等等的操作,这些操作通常也会融入在上边的三个大的步骤里边。 自定义控件需要我们多动手练,多研究别人的代码。这些理论性的东西只有真正使用到的时候才能印象深刻。

chinwetang commented 5 years ago

关于View的测量,布局,绘制上面很多同学都讲的差不多了,我谈谈自定View的使用场景及类别吧;

  1. 组合View 对于UI比较复杂的页面的优化,我们拥有很多手段,从上面来讲,最少我们可以拆成多个fragment,也可以拆成多个大View,记得凯哥讲过,MVC与MVP从概念上来讲并没有太大的区别,实际使用上MVP是分离的更好的MVC,由于MVC本身的C和V高度耦合,所以我们可以考虑把C拆出来变成presenter,也可以考虑把View通过组合的方式拆出去变成一个大View

  2. 修改View 有些时候我们想用android自带的view但是却有一些痛点难以接受,这个时候我们可以通过小修小改来解决,比如固定长度的ListView,正方形的ImageView等等;

  3. 自定义View 这个就是上面很多同学讲到的一整个Measure - Layout - Draw流程了,以凯哥课上讲的TabLayout为例我们不难发现,作为一个ViewGroup出现的时候,往往需要在Measure - Layout这两步下大功夫,而作为View出现的时候可能就要转而在Draw上做更多的事情;当然事无绝对,比如处理滑动冲突的时候可能子父View都需要额外做一下其它的事情,比如专注于事件处理的View,可能不需要绘制,但是需要在onTouchEvent里面做更多的事情。

SeniorDoctorHui commented 5 years ago

clipboard

MeasureSpec有三种测量模式

  1. UNSPECIFIED 父控件对子控件不加任何束缚,子元素可以得到任意想要的大小,这种MeasureSpec一般是由父控件自身的特性决定的。比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示,这个时候,size一般就没什么意义。
  2. EXACTLY 父控件为子View指定确切大小,希望子View完全按照自己给定尺寸来处理,这时View的大小就是SpecSize指定的值
  3. AT_MOST 父控件为子元素指定最大参考尺寸,希望子View的尺寸不要超过这个尺寸,它对应于LayoutParams中的wrap_content