Android技能树 — View小结

前言

最近年末了,打算把本身的Android知识都整理一下。java

Android技能树系列:android

Android基础知识git

Android技能树 — 动画小结github

Android技能树 — View小结面试

Android技能树 — Activity小结算法

Android技能树 — View事件体系小结canvas

Android技能树 — Android存储路径及IO操做小结数组

Android技能树 — 多进程相关小结bash

Android技能树 — Drawable小结数据结构

Android技能树 — Fragment整体小结

数据结构基础知识

Android技能树 — 数组,链表,散列表基础小结

Android技能树 — 树基础知识小结(一)

算法基础知识

Android技能树 — 排序算法基础小结

此次是相对View作个小结,主要是View的工做原理,绘制流程等。为何要总结这块,由于平时自定义View的状况多多少少都会遇到,若是能深入了解这块知识,对自定义View的掌握才能更透彻。有些人可能会说那我确定不会的,我也不用看这个总结文章了,不要紧,我此次写的很简单,基本你们都能理解。看完后,你们应该都会本身写效果不复杂的自定义View和自定义ViewGroup。

PS: 非广告。我自己View的相关知识也是之前从其余地方学到的。我比较推荐这块内容看(Android开发艺术探索 和 扔物线的View相关内容。因此文中有些的知识点也会引用这二块地方。)

以下图所示:我主要是整理了这些相关知识:

脑图下载连接

View小结


咱们能够看大分类:

咱们知道一个View要绘制好,是要有三步的(我估计百分之99.9的人都知道这三步): measure测量,layout肯定位置,而后draw画出来。因此我此次也是主要这三步来讲明的。而你们可能看到这里有一个额外的ViewRoot的知识点,主要是给前面的三步作个补充知识。

ViewRoot(补充知识)

ps:不看其实问题也不大,不想了解的直接看本文的主要的measure,layout,draw三步曲。

ViewRoot字面意思是否是让你感受是整个ViewTree的根节点。错!ViewRoot不是View,它的实现类是ViewRootImpl,它是DecorViewWindowManager之间的纽带。因此ViewRoot更恰当来讲是DecorView的“管理者”。

(PS:下次面试官问你ViewRoot是啥,你可别说是ViewTree的根节点。哈哈。)

因此这时候既然开始整个界面要绘制了。明显就是ViewRoot开始发起调用方法,毕竟“管理者”么。因此View的绘制流程是从ViewRootperformTraversals方法开始的。因此performTraversals方法依次调用performMeasure,performLayoutperformDraw三个方法。由于这三个方法及后面的方法调用都差很少,咱们以performMeasure为例,performMeasure会调用measure方法,而measure方法又会调用onMeasure方法(PS:是否是就发现了为啥咱们平时都是重写onMeasure方法了。),而后又会在onMeasure方法里面去调用全部子View的measure过程。

咱们能够看到思惟脑图中有提到顶级View就是DecorView,那DecorView是什么呢? DecorView是一个FrameLayout,里面包含了一个竖向的LinearLayout,通常来讲这个LinearLayout是有上下二部分(这里具体跟Android SDK和主题有关):

是否是看到了熟悉的Content这个名字,没错。咱们在Activity里面设置布局setContentView就是把咱们的布局加到这个id为android.R.id.contentFrameLayout里面。

咱们如今正式进入View整个绘制流程:

View的大小

你们能够看到,为了方便你们理解,我写了二个现实生活场景故事对比。

故事对比<1>

咱们能够看到,咱们的气球放到柜子里面,决定气球大小的因素有二个:柜子给它的限制,还有它自身的因素(质量好坏,好的能吹的很大)。而咱们的View也是同样的,首先咱们用MeasureSpec来决定咱们的View大小,那咱们的MeasureSpec和睦球同样,也受到二个因素的影响:

  1. ViewGroup的影响
  2. 自身的LayoutParams

总结起来就是一句话:在测量过程当中,系统会将View的LayoutParams根据父容器ViewGroup所施加的规则下,转换得出相对应的MeasureSpec,而后根据这个MeasureSpec来测量出View的高/宽。

可能你们会问什么是MeasureSpec,别急,咱们立刻就来介绍

MeasureSpec知识

其实直接看脑图,应该就能看得懂吧,主要是这么几个知识点:

  1. MeasureSpec是由SpecMode和SpecSize组合成的。
  2. SpecMode的种类:UNSPECIFIED,EXACTLY,AT_MOST。
  3. 普通的View是由父容器限制和自身的LayoutParams来生成相应的MeasureSpec,而DecorView由于是顶层View了。咱们能够想象哪来的父容器啊,在外面一层就直接是屏幕了,因此是由屏幕的尺寸和自身的LayoutParams决定。

对比故事<2>

没错,经过对比,咱们能够发现规律原来很简单。由于咱们脑子里面能够用这个气球的对比故事更好的理解。

我作一个总结表格:(要理解上面的分析过程,而不是背下这个表格,背下来没啥意思)

View的测量

经过上面咱们已经知道MeasureSpec是用来肯定View的测量的,也已经能根据不一样的状况来得到相应的MeasureSpec了。那咱们的到底应该在哪里去建立MeasureSpec呢?而后给子View去约束呢?

其实奥秘就在咱们平时重写的onMeasure()方法中:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}        
复制代码

咱们是否是看到了onMeasure方法里面传入了(int widthMeasureSpec, int heightMeasureSpec),没错,这里传入的二个参数,就是当前你重写这个方法的所在的View(子View或者ViewGroup)的进行过一系列的操做最后得到的MeasureSpec。

那咱们拿到这二个参数后,View仍是不知道咱们到底给它的宽和高是多少。应该确定最后是咱们调用类型:view.setMeasureWidth(XX),view.setMeasureHeight(XX)这样,它才能被设置测量的宽和高。没错,setMeasuredDimension(int measuredWidth, int measuredHeight)方法就是咱们用来设置view的测量宽和高。

固然你可能会问,那我若是直接调用这个方法来设置view的宽和高,那我感受我不用MeasureSpec都不要紧啊。好比下面的代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //没有使用相应的MeasureSpec
    setMeasuredDimension(100,100);
}
复制代码

没错,咱们能够不是经过正规的测量过程来决定测量的宽和高,咱们就是任性的直接定了宽高是100。可是这样就不符合规则流程了。并且作出来的东西也不会特别好。好比这时候,你在xml中对你的view设置match_parent,wrap_content,200dp就会都无效,由于代码最后都是用了100。

onMeasure()方法的构成

咱们前面提过,自定义View是要重写onMeasure()方法的,咱们再仔细分析下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    //咱们通常会本身写的代码
    ........
    ........
    .......
    
}
复制代码

咱们能够看到,主要分为二块:

  1. super.onMeasure(),
  2. 本身写的代码。

咱们根据不一样的状况一步步来看这些代码的做用。

直接继承View.java

super.onMeasure() 分析1 :好比咱们的自定义View直接继承了View.java:

public class DemoView extends View {
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    }
}

复制代码

咱们能够查看super.onMeasure方法:

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

咱们看到果真调用了setMeasureDimension方法来进行宽高的设置了。


PS:接下来的源码这个分析能够不看,直接看结论。嘿嘿。嘿嘿。我知道不少人都不想看。

咱们能够看到主要是三个方法(咱们这里就看width的测量):

  1. 先getSuggestedMinimumWidth方法获取了某个值。
  2. 经过getDefaultSize方法来对第一步获取到的值和约束一块儿处理后,获得最终值。
  3. 经过setMeasuredDimension方法把咱们最终的值给赋值进去。

1和2的方法先不看,咱们起码知道了。咱们最终肯定一个View的测量大小,是经过setMeasuredDimension来设置的(其实我感受我说的废话,看这个方法的名字就很明确了)。

咱们再回头来看1中的方法:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
复制代码

若是咱们的View没有设置background,则返回的最小值为mMinWidth(啥是mMinWidth?????就是咱们在xml设置的android:minWidth的值)。若是咱们设置了background,则获取mBackground.getMinimumWidth()(其实这个方法就是返回Drawable的原始宽度)。最后返回max(mMinWidth, mBackground.getMinimumWidth())两者中的最大值。

咱们再来看2中的方法:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
}
复制代码

其实上面咱们的MeasureSpec的建立规则会的话,其实应该就能看的懂。若是是specMode是UNSPECIFIED,则返回咱们1中的方法getSuggestedMinimumWidth获取到的值,若是是AT_MOSTEXACTLY,则直接返回specSize。(View源码这里的宽度的建立规则和咱们前面讲的测量的规则区别就在于,当specMode是UNSPECIFIED的时候,返回的是getSuggestedMinimumWidth的值,而咱们是返回了0。)

结论1:若是写的自定义View是直接继承View的,并且写了super.measure(),则会默认给这个View设置了一个测量宽和高(这个宽高是多少?若是没有设置背景,则是xml里面设置的android:minWidth/minHeight(这个属性默认值是0),若是有背景,则取背景Drawable的原始高宽值和android:minWidth/minHeight两者中的较大者。)

继承现有控件

super.onMeasure() 分析2 :好比咱们的自定义View继承了现有的控件,好比ImageView.java:

public class Image2View extends ImageView {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
复制代码

这时候咱们的super.onMeasure()方法调用的就是ImageView里面的onMeasure方法了:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //ImageView 的一大堆计算宽高的代码。
    ......
    ......
    ......
    
    //固然最终确定要把算好的宽高告诉View
    setMeasuredDimension(widthSize, heightSize);
}
复制代码

咱们发现若是咱们的View直接继承ImageView,ImageView已经运行了一大堆已经写好的代码测出了相应的宽高。咱们能够在它基础上更改便可。

好比咱们的Image2View是一个自定义的正方形的ImageView,:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //这里已经帮咱们测好了ImageView的规则下的宽高,而且经过了setMeasuredDimension方法赋值进去了。
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //咱们这里经过getMeasuredWidth/Height放来获取已经赋值过的测量的宽和高
    //而后在ImageView帮咱们测量好的宽高中,取小的值做为正方形的边。
    //而后从新调用setMeasuredDimension赋值进去覆盖ImageView的赋值。
    //咱们从头到位都没有进行复杂测量的操做,全靠ImageView。哈哈
    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    if (width < height) {
        setMeasuredDimension(width, width);
    } else {
        setMeasuredDimension(height, height);
    }
}
复制代码

结论2:若是写的自定义View是继承现有控件的,并且写了super.measure(),则会默认使用那个现有控件的测量宽高,你能够在这个已经测量好的宽高上作修改,固然也能够所有从新测过再改掉。

本身写的代码与super.measure的先后位置

super.onMeasure() 分析3:咱们写的本身的代码与super.measure的先后位置关系

咱们能够看到,无论你是继承View仍是现有的控件(好比ImageView),super.onMeasure()中都默认会按照本身的逻辑测量一个宽和高,而后调用setMeasuredDimension()方法赋值进去。

  1. 若是咱们的本身的代码写在super.measure前面,那么你写的测量的逻辑测定好宽高,而且赋值后,最终都会再次被super.measure中的setMeasuredDimension()所覆盖。
  2. 若是咱们的本身的代码写在super.measure后面,你能够在你继承的父类的测量结果的基础进行更改(固然你不用父类的测量结果也是不要紧的),而后再次调用setMeasuredDimension()赋值。
  3. 若是你的测量宽高的逻辑,不是基于你继承的控件的测量的基础上进行,彻底由你来从新测定的话,super.onMeasure()不写也不会有问题。

具体实现自定义View的测量

1. 好比咱们直接是继承现有的控件,好比ImageView,实现一个正方形的ImageView(上面已经提到过了):
public class Image2View extends ImageView {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //这里的super.onMeasure()方法里面,已是调用了ImageView的onMeasure()方法。
        //因此已经进行了测量了。而且在这个方法最后调用了setMeasuredDimension(widthSize, heightSize);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //因此你不写任何东西,这个测量结果都已经肯定过了,由于已经执行过了setMeasuredDimension。
        //但好比你想要在ImageView的基础上,让这个ImageView变成一个正方形的ImageView。
        //由于测出来的宽高可能不一样,是一个矩形。咱们就须要手动的再去设置一次宽和高。
        int width = getMeasuredWidth();//获取ImageView源码里面已经测量好的宽度
        int height = getMaxHeight();//获取ImageView源码里面已经测量好的高度
        if (width < height) {
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(height, height);
        }
    }
}

复制代码

咱们发现,咱们是在已经咱们继承的现有的控件帮咱们测量好宽高后,能够再次在这个已经测量好的宽高的基础上进行更改。咱们并无用到咱们前面学到的MeasureSpec的知识,由于super.onMeasure()中已经帮咱们把MeasureSpec处理好了。

2. 好比咱们本身直接继承了View:
public class CircleView extends View {
    public CircleView(Context context) {
        super(context);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        //View测量宽高的三步曲
    
        //1.设置默认值,wrap_content的状况下的值。
        //由于wrap_content只是说不超过某个最大值,若是不设置默认值,效果与Match_parent同样了。
        int defaultWidthSize = 200;
        int defaultHeightSize = 200;
        
        //2.调用resolveSize()方法,把MeasureSpec和咱们的默认值放进去
        //这个方法返回一个最终根据你传入的默认值及MeasureSpec共同做用后的最终结果
        defaultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec);
        defaultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec);
        
        //调用setMeasuredDimension方法赋值宽和高
        setMeasuredDimension(defaultWidthSize, defaultHeightSize);

    }
}
复制代码

是否是超级超级超级简单。你们可能就会问,那个resolveSize()方法是什么,怎么这么神奇。

PS:下面的resolveSize()源码分析不看也没啥关系,反正会用就好了。哈哈,不影响使用。

咱们能够来看下它的源码:

public static int resolveSize(int size, int measureSpec) {
    return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}


public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {

    //1.拿到specMode 和 specSize

    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    
    //2.根据不一样的specMode来进行判断最终值是什么
    switch (specMode) {
    
        
        case MeasureSpec.AT_MOST:
            /*
                2.1若是specMode是AT_MOST模式,咱们原本应该直接是specSize
                可是若是咱们的默认值比咱们的specSize大就很尴尬了。气球默认的大小都装不进柜子了。这时候咱们View的大小要设置成specSize,若是默认大小比咱们的specSize小就不要紧,直接为默认值。
            */
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
            
        /*
            2.2若是是EXACTLY,直接就是specSize    
        */
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
            
         /*
           2.3若是是UNSPECIFIED模式,则直接就是咱们设的默认值
         */
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
复制代码
3. ViewGroup的测量

在讲ViewGroup的测量前面,我要提问个问题,你们应该知道了某个View的MeasureSpec在是在onMeasure()方法的参数里面传进来的。咱们是直接拿来用了。那又是那里调用了onMeasure()方法帮忙把这二个参数带进来的呢。这二个参数又是哪里生成的呢?

答案就是这个子View的父容器给它的。父容器在他本身的onMeasure()方法里面会根据本身的onMeasure()传进来的MeasureSpec,及这个子View的自身的LayoutParams状况,生成相应的childMeasureSpec,而后调用子View的measure()传递进去的(前面提过,measure()方法会调用onMeasure()方法。)

好比咱们写一个圆形排布的ViewGroup(LinearLayout是一排的排布)。

public class CircleLayout extends ViewGroup {
    
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        
        //1.父容器的onMeasure()传进来的二个参数widthMeasureSpec和 heightMeasureSpec
        //2.还差子View的LayoutParams,获取子View的LayoutParams
        //3.经过两者产生新的MeasureSpec而后给子View。
        //4.而产生的新的ChildMeasureSpec的规则就是咱们前面表格总结过的规则。
        
        /*
        PS:下面这段是我写的代码,并非正确的,由于父容器可能包含多个子View,
        因此到某个子View的时候,给它的specSize应该是父容器的剩余空间,
        因此传入的父容器的可用空间原本是不停的减小的,外加还有margin,padding值也要减去。
        我就是主要意思下,让你们懂得原理。
        */
        
        
        //先判断初始时候父容器的大小,由于父容器也是个View,因此也是三步曲。
        //设置默认值(能够是0,由于父容器通常默认不会占有空间)
        int defaultWidthSize = 500;
        int defaultHeightSize = 500;
        //resolveSize处理获取宽和高
        int resultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec);
        int resultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec);
        
        //好比咱们这里以width为例子:
        //咱们前面提过了,最终给子View的MeasureSpec是由父View的MeasureSpec与子View的LayoutParam共同肯定。
        //先获取父View的MeasureSpec的mode和size
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
            
        //根据不一样的SpecMode及子View的LayoutParams来产生新的ChildMeasureSpec。
        
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            
            LayoutParams params = view.getLayoutParams();
            int childWidthSpec, childHeightSpec;
            
            //先根据父View的MeasureSpec来进行大分类:
            switch (specMode) {
        
                case MeasureSpec.EXACTLY:
                    //说明是固定值,好比100dp等
                    if (params.width >= 0) {
                        resultSize = params.width;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {
                        
                        resultSize = specSize;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              
                        resultSize = specSize;
                        resultMode = MeasureSpec.AT_MOST;
                    }
                
                    break;
                
                case MeasureSpec.AT_MOST:
                    .....
                    .....
                    break;
                    
                case MeasureSpec.UNSPECIFIED:
                    .....
                    .....
                    break;
                
                
            }
            
             childWidthSpec = MeasureSpec.makeMeasureSpec(resultWidthSize, MeasureSpec.EXACTLY);
             
             getChildAt(i).measure(childWidthSpec, childHeightSpec);
            
        }
        
        
        
        /*
            可能有人说,生成新的规则我都懂,可是每次都要写上面一大段的代码,
            我不想写自定义ViewGroup了。我仍是放弃吧,别急,你们也发现上面的规则的确是固定的。
            那有没有相似咱们在上面设置本身宽高时候的相似resolveSize的方法呢。
            若是没有特定的需求,的确咱们不须要写上面一大段。
            有二种方法。
         
        */
        
        
        //方法1:能够经过调用measureChildren()一会儿把全部的子View测量好
         measureChildren(widthMeasureSpec, heightMeasureSpec);
        
        //方法2:经过measureChild()一个个来测量。
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            measureChild(view , widthMeasureSpec,heightMeasureSpec);
        }    
        
        //设置父容器的大小
        setMeasuredDimension(XXXX,XXXX)
    
    }
}
复制代码

没错,最后咱们能够用measureChildren(widthMeasureSpec, heightMeasureSpec);measureChild(view , widthMeasureSpec,heightMeasureSpec);方法来,咱们也知道它的内部确定也是根据相应的规则,生成对应的childMeasureSpec,而后调用child的measure方法。

咱们能够看下源码(PS:不想看仍是不要紧,能够跳过):

//measureChildren其实只是帮咱们遍历了全部的View,帮咱们把可见的View分别调用measureChild方法来处理。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

//而measureChild方法里面就是获取子View的LayoutParams和传进来的MeasureSpec,
//把这两者经过getChildMeasureSpec方法得到一个新的childMeasureSpec,而后传给child.measure方法。
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
           mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码

若是具体想看getChildMeasureSpec作了什么,可有再去看下源码,可是他们生成的规则跟咱们前面讲的仍是同样的。我这里很少说了。

测量完后获取View的宽和高

这个就十分简单了。直接看脑图便可。

View的位置

这块比较简单,我也很少说了。(别吐槽我,这文章太多了。写太多没人会耐心看完。)

View的绘制

咱们都知道View的大小和位置都肯定好了,确定就差绘画了。

View 绘画draw()

咱们都知道是经过draw()方法来绘制的。

而draw()方法具体作了什么呢,咱们能够看源码这个方法的工做过程的介绍:

draw()源码里面的介绍:

/*
 * Draw traversal performs several drawing steps which must be executed
 * in the appropriate order:
 *
 *      1. Draw the background
 *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
 *      4. Draw children
 *      5. If necessary, draw the fading edges and restore layers
 *      6. Draw decorations (scrollbars for instance)
 */
复制代码

分别是先绘制背景,而后绘制本身的内容,而后绘制子View的内容,最后画装饰和前景。

推荐你们看扔物线大佬的文章,讲的很清楚,我就不花大篇幅写基础了。

HenCoder Android 自定义 View 1-5: 绘制顺序

Canvas的使用

咱们知道不论是onDraw(Canvas canvas),dispatchDraw(Canvas canvas),onDrawForeground(Canvas canvas)等都是参数是Canvas(画布)。因此咱们知道了是用Canvas来绘画。

这里也是推荐扔物线大佬的相关文章,讲的很细,我也再也不大篇幅的写各类基础使用知识。

HenCoder Android 开发进阶: 自定义 View 1-1 绘制基础

HenCoder Android 开发进阶:自定义 View 1-4 Canvas 对绘制的辅助

Canvas怎么使用呢: 主要分为二大块:

Canvas绘制类方法

这块很简单,直接用Canvas来画颜色,画矩形,画圆形,画直线等各类图形。虽然简单,但毕竟这才是基本的绘制,用的最多。

Canvas的辅助类方法

其中几何变化又分为二维变换和三维变换:

二维变换

三维变换

Paint相关

咱们知道Paint是画笔,咱们能够设置颜色,画笔粗细等。

继续推荐扔物线大佬的相关文章(基础我就不写了):

HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解

颜色相关

效果

绘制文字相关

Paint初始化部分相关


结语

有错误的地方,请你们轻点喷,我胆子很小的。。。。

相关文章
相关标签/搜索