自定义控件及相关知识点详解

        在平时的开发过程中,我们常常都是使用系统提供的相关控件,如果在andorid.widget中找不到相关的控件,我们也常常也已在github上找到相关的开源控件,但是当我们发现,我们需要二次开发已有的控件或者github上面的开源控件去满足我们变化的业务时,我们往往需要了解自定义控件的一些知识点,甚至当我们需要去自定义一个全新的自定义控件,这就需要我们对自定义控件有个更加深入的过程,下面我就列出一些本人觉得比较重要的一些自定义控件的知识点,里面用到的类主要集中在Android.widget文件夹中的类。   


知识点汇总:

一:View视图树的结构与自定义控件的关系

二:常见的自定义控件的相关函数

三:自定义控件中View与ViewGroup类的重要函数介绍

四:Paint类详解

五:Point与PointF类的比较与详解

六:Rect与RectF类的比较与详解

七:Canvas类详解与重要函数分析

八:Path类的详解

九:Camera类的详解

十:Shader类(着色器)及其继承类的知识点详解

十一:Matrix类的详解

十二:自定义控件ProgressTimeBar源码分析

十三:自定义控件TagFlowLayout源码分析

十四:自定义控件FishDrawable源码分析

十五、如何优化自定义View

十六:扩展阅读


一:View视图树的结构与自定义控件的关系

概述:

   Android自己的SDK提供了2D图形处理相关的API,大部分都在android.graphics和android.graphics.drawable包中。

   绘制2D图形,有两种方式:在layout中定义图形和动画,主要用于绘制静态图形和预先定义好的动画,直接在Canvas上绘制,一般用于需要重绘实现效果的View。

根据官方文档,绘画需要4个元素协同完成:

1、位图:Bitmap来存储像素。

2、画布:Canvas来响应画画(draw)的行为并将其写入Bitmap。

3、颜料:drawing primitive,比如矩形、路径、文字、位图等元素。

4、画笔:Paint描述画画(draw)的颜色和样式等。


1.1、Android提供给用户的常见控件的区分

   查看了一下Android系统中常见的系统控件,这些控件的依赖一般分为三种:

1、在Android.jar包里面的控件(Button、TextView等控件)

2、在Material.aar依赖包的控件(floatingActionButton等控件)

3、单独的依赖包控件(CoordinatorLayout、CardView等控件)


1.2、自定义控件的常见分类

1、组合控件:继承容器控件(RelativeLayout,FrameLayout),实现不同控件的组合

2、二次开发控件:继承已有的控件做二次开发(继承ImageView,ViewPager等系统控件)

3、自定义View控件:直接继承视图控件View或ViewGroup,自己实现控件的测量,布局,绘制

附加:自定义控件的分类方式根据不同人的理解,会有不同的区分方法


1.3、Android系统视图树汇总


1.4、关于系统文件夹相关的类汇总

二:常见的自定义控件的相关函数

2.1、自定义View绘制流程函数调用链


2.2、测量View大小(onMeasure)

1、View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量,测量View大小使用的是onMeasure函数,我们可以从onMeasure的两个参数中取出宽高的相关数据:

代码示例:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int widthsize = MeasureSpec.getSize(widthMeasureSpec);      //取出宽度的确切数值

    int widthmode = MeasureSpec.getMode(widthMeasureSpec);      //取出宽度的测量模式

    int heightsize = MeasureSpec.getSize(heightMeasureSpec);    //取出高度的确切数值

    int heightmode = MeasureSpec.getMode(heightMeasureSpec);    //取出高度的测量模式

}

   从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值:

   测量模式一共有三种, 被定义在Android中的View类的一个内部类View.MeasureSpec中。

注意:如果对View的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要调用 setMeasuredDimension( widthsize, heightsize); 这个函数,其实要确认控件的大小需要结合父控件的三种模式和自身的三种模式,组合起来是9种模式。

MeasureSpec类详解

概述:

       当View测量自身的大小的时候,会执行measure(int widthMeasureSpec, int heightMeasureSpec)方法,至于measure方法内容怎么执行的,这里先不去探讨。注意方法中两个参数,它们其实是一个int 类型的MeasureSpec。MeasureSpec可以说是View测量过程的前提,所以我们很有必要先来了解一下MeasureSpec。

MeasureSpec工作原理:

MeasureSpec 代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。

SpecMode是指测量模式,SpecSize是指在某种测量模式下的大小。

MeasureSpec是View中的一个静态内部类。


SpecMode有三类:

UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这般情况一般用于系统内部,表示一种测量状态,如ScrollView测量子View时用的就是这个。

EXACTLY:父容器已经检测出View所需要的大小,这个时候View的最终大小就是SpecSize所测定的值,它对应于LayoutParams中的match_parent和具体的数值(如40dp,60dp)这两种模式。

AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。


2.3、确定View大小(onSizeChanged函数)

解析:这个函数在视图大小发生改变时调用,在测量完View并使用setMeasuredDimension函数之后View的大小基本上已经确定了,那么为什么还要再次确定View的大小呢?

   这是因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。

函数onSizeChanged():

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

    super.onSizeChanged(w, h, oldw, oldh);

}

解析:可以看出,它有四个参数,分别为 宽度,高度,上一次宽度,上一次高度,这个函数比较简单,我们只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。


2.4、确定子View布局位置(onLayout)     

   确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数,在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。 child.layout(l, t, r, b); 四个参数分别为:


2.5、绘制内容(onDraw)

     onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。

@Override

protected void onDraw(Canvas canvas) {

    super.onDraw(canvas);

}


三:自定义控件中View与ViewGroup类的重要函数介绍

3.1、invalidate,postinvalidate和requestLayout的区别及使用

RequestLayout:

当我们动态移动一个View的位置,或者View的大小、形状发生了变化的时候,我们可以在view中调用这个方法,即:view.requestLayout();

Invalidate:

该方法的调用会引起View树的重绘,常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法。

PostInvalidate:

这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

总结:如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效。


四:Paint类详解

Paint常用函数:

1、reset():重置画笔

2、setColor(int color):给画笔设置颜色值

3、setARGB(int a, int r, int g, int b):同样是设置颜色,但是利用ARGB分开设置

4、setAlpha(int a):设置画笔透明度

5、setStyle(Paint.Style style):设置画笔样式,取值有

Paint.Style.FILL :填充内

Paint.Style.FILL_AND_STROKE :填充内部和描边

Paint.Style.STROKE :仅描边

6、setStrokeWidth(float width):设置画笔宽度

7、setAntiAlias(boolean aa):设置画笔是否抗锯齿

8、setStrokeCap(Paint.Cap cap):设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)

9、setStrokeJoin(Paint.Join join):设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)

10、setStrokeMiter(float miter):设置笔画的倾斜度,90度拿画笔与30拿画笔,画出来的线条样式肯定是不一样的吧。

11、setPathEffect(PathEffect effect):设置路径样式,取值类型是所有派生自PathEffect的子类:ComposePathEffect, CornerPathEffect, DashPathEffect, DiscretePathEffect, PathDashPathEffect, SumPathEffect


二、字体相关

1、setTextSize(float textSize):设置文字大小

2、setFakeBoldText(boolean fakeBoldText):设置是否为粗体文字

3、setStrikeThruText(boolean strikeThruText):设置带有删除线效果

4、setUnderlineText(boolean underlineText):设置下划线

5、setTextAlign(Paint.Align align):设置开始绘图点位置

6、setTextScaleX(float scaleX):水平拉伸设置

7、setTextSkewX(float skewX):设置字体水平倾斜度,普通斜体字是-0.25,可见往右斜

8、setTypeface(Typeface typeface):字体样式

9、setLinearText(boolean linearText):设置是否打开线性文本标识;由于文本想要快速绘制出来,必然是需要提前缓存在显存中的,一般而言每个文字需要一个字节的大小来存储它(当然具体需要多少字节与编码方式有关),那如果是长篇文章,可见所需的大小可想而知。我们可以通过setLinearText (true)告诉Android我们不需要这样的文本缓存。但如果我们不用文本缓存,虽然能够省去一些内存空间,但这是以显示速度为代价的。

由于这个是API 1的函数,由于当时的android手机的内存大小还是很小的,所以尽量减少内存使用是每个应用的头等大事,在当时的的环境下这个函数还是很有用的。

10、setSubpixelText(boolean subpixelText):表示是否打开亚像素设置来绘制文本。亚像素的概念比较难理解,首先,我们都知道像素,比如一个android手机的分辨率是1280*720,那就是指它的屏幕在垂直方向有1280个像素点,水平方向上有720个像素点。我们知道每个像素点都是一个独立显示一个颜色的个体。所以如果一副图片,在一个屏幕上用了300*100个相素点,而在另一个屏幕上却用了450*150个像素来显示。那么,请问在哪个屏幕上这张图片显示的更清晰?当然是第二个屏幕,因为它使用的像素点更多,所显示的细节更精细。


三:图像处理与测量相关

1、setShader(Shader shader)

2、setShadowLayer(float radius, float dx, float dy, int shadowColor)

3、setDither(boolean dither)

4、setColorFilter(ColorFilter filter)

5、setXfermode(Xfermode xfermode)

6、setFilterBitmap(boolean filter)

7、clearShadowLayer()

8、breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth)

9、measureText(String text)


五:Point与PointF类的比较与详解

解析:Point 类是一种最简单的结构,代表一个“点”,实现了 Parcelable 接口,支持序列化与反序列化。Point 类定义了两个 int 成员 x 和 y,代表点的 x 坐标和 y 坐标,图形坐标系与数学中的平面坐标系有所不同,x 方向向左为负,向右为正,y 方向向上向负,向下为正,图形坐标系的原点在左上角,这一点要特别注意,所以,默认情况下,当 x、y 为正数时该点会显示在屏幕之内(取决于屏幕大小),如果为负数就显示在屏幕之外。

    在Android 中,通过相应的 API 是可以对图形坐标系进行平移和旋转的,PointF 类和 Point 类的定义是完全一样的,最大的不同就是成员变量 x、y 的类型不是 int 而是 float,这也是加了后缀“F”的原因。不过,PointF ᨀ供了一个很贴心的功能,定义了 length()方法计算坐标原点(0,0)到(x,y)之间的距离。


六:Rect与RectF类的比较与详解

6.1、概述

  Rect的参数为int类型,而RectF的参数类型为float类型,从这一点上来看,RectF的精度更高一些,但是他们都是通过四个坐标参数来确定一个矩形的区域。

6.2、RectF一共有四个构造方法:

1、RectF()构造一个无参的矩形。

2、RectF(float left,float top,float right,float bottom)构造一个指定了4个参数的矩形。

3、RectF(RectF r)根据指定的RectF对象来构造一个RectF对象(对象的左边坐标不变)。

4、RectF(Rect r)根据给定的Rect对象来构造一个RectF对象。

6.3、RectF提供了很多方法,下面介绍几个方法:

1、Public Boolean contain(RectF r);判断一个矩形是否在此矩形内,如果在这个矩形内或者和这个矩形等价则返回true,同样类似的方法还有public Boolean contain(float left,float top,float right,float bottom)和public Boolean contain(float x,float y)。

2、Public void union(float x,float y)更新这个矩形,使它包含矩形自己和(x,y)这个点。

3、Rect和RecF的用法基本类似,只是参数为int类型,Rect(int left,int top,int right,int bottom),所取得的图形区域为weight(right - left),height(bottom - top),在绘制的时候要注意这四个坐标之间的关系。


七:Canvas类详解与重要函数分析

概述:我们要实现自定义的图形绘制或者动画效果,就必须通过Canvas。通过Canvas,我们所有draw的效果都会被执行到一个Bitmap上面,而这个Bitmap就会显示到手机上,被我们看到。 所以说,Canvas只是提供绘图的API,真正的内存是下面的Bitmap。 android.graphics.Canvas 类提供了很多“画“的方法,让这块画布具有了丰富多彩的画画能力。比如:画点、线、矩形、椭圆、圆、文字等等。

7.1、Canvas的常用函数

7.2、画布操作

解析:为什么要有画布操作?

画布操作可以帮助我们用更加容易理解的方式制作图形。

例如:从坐标原点为起点,绘制一个长度为20dp,与水平线夹角为30度的线段怎么做?

   按照我们通常的想法(被常年训练出来的数学思维),就是先使用三角函数计算出线段结束点的坐标,然后调用drawLine即可。

   假设我们先绘制一个长度为20dp的水平线,然后将这条水平线旋转30度,则最终看起来效果是相同的,而且不用进行三角函数计算,这样是否更加简单了一点呢?

   合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果,这也是画布操作存在的原因。

备注:所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响。


7.3、快照(save)和回滚(restore)

解析:为什么存在快照与回滚,画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,例如第一个例子,两个圆形都是在坐标原点绘制的,而因为坐标系的移动绘制出来的实际位置不同,所以会对画布的一些状态进行保存和回滚。

Canvas 顾名思义画布,其成员函数save()、restore()分别为保存恢复状态。

save():保存当前的Matrix和clip放在私有栈里面。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作,而不会影响到save之前的操作,也就是说你旋转了,但是save之前画出来的东西不会旋转。放缩,平移、错切、裁剪也是如此。

restore():和save()是对应起来的,他是用来在之前save过的画布上清除掉save之后所有的修改。也就是save之后的一些操作对save之前的操作没有影响。

    坐标系的变换操作只对save()和restore()之间的操作有效,比如说旋转之后,在没有调用restore()之前都是以右上角为坐标系原点。而restore()之后又旋转回来了。

    坐标系的变换操作只对save()和restore()之间的操作有效,比如说旋转之后,在没有调用restore()之前都是以右上角为坐标系原点。而restore()之后又旋转回来了。


相关函数:

解析:这个栈可以存储画布状态和图层状态,什么是画布和图层,实际上我们看到的画布是由多个图层构成的,

实际上我们之前讲解的绘制操作和画布操作都是在默认图层上进行的。 在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)等,则分图层绘制比较好一些。 你可以把这些图层看做是一层一层的玻璃板,你在每层的玻璃板上绘制内容,然后把这些玻璃板叠在一起看就是最终效果。

restore函数:

  状态回滚,就是从栈顶取出一个状态然后根据内容进行恢复。

  同样以上面状态栈图片为例,调用一次restore方法则将状态栈中第5次取出,根据里面保存的状态进行状态恢复。

restoreToCount函数:

  弹出指定位置以及以上所有状态,并根据指定位置状态进行恢复。

  以上面状态栈图片为例,如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态,并根据第2次保存的状态进行恢复。

getSaveCount函数:

  获取保存的次数,即状态栈中保存状态的数量,以上面状态栈图片为例,使用该函数的返回值为5。

  不过请注意,该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态。

示例:

虽然关于状态的保存和回滚啰嗦了不少,不过大多数情况下只需要记住下面的步骤就可以了:

save();      //保存状态

...          //具体操作

restore();   //回滚到之前的状态

这种方式也是最简单和最容易理解的使用方法。


八:Path类的详解

常用API解析:

8.1、xxxTo方法 Path类中提供了一套xxxTo方法,其作用是从起点到终点移动path画笔并绘制线(moveTo方法只移动path画笔不绘制线),线有直线和曲线。


8.2、rXxxTo方法

    rXxxTo方法的r意思是relative,即相对的意思,方法有四个,如上图所示,其功能与对应的xxxTo方法一样,区别在于rXxxTo方法在绘制Path时是以当前path画笔位置为坐标原点,即相对于path画笔位置进行绘制,而xxxTo方法的坐标原点则与当前canvas坐标原点一致。


8.3、addXxx方法

    Path类中还提供了一套addXxx方法,字面理解就是添加一段相应的线,线可以是曲线、完整的圆形、矩形等,甚至可以是另一组Path的线。所谓添加的意思,我个人理解就是在绘制这段线前,移动(moveTo)path画笔位置到线的起始位置,然后再绘制线,也就是说添加的这段线,与之前绘制的Path是分离的(除非后绘制的这段线的起始点与之前Path的终点一致)。


8.4、其他方法

九:Camera类的详解

解析:graphic包下的Camera,专业给View拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。

   众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,实际上这个类更像是一个操作Matrix的工具类,使用Camera和Matrix可以在不使用OpenGL的情况下制作出简单的3D效果。

Camera常用方法表:

项目实例:

1、https://github.com/JeasonWong/FlipShare

2、https://github.com/ImmortalZ/StereoView(3D立体旋转容器)

效果图:

十:Shader类(着色器)及其继承类的知识点详解

概述:Shader类是专门用来渲染图像及一些几何图形的,使用Shader时,先构建Shader对象,然后通过Paint的setShader()方法来设置渲染对象,最后将这个Paint对象绘制到屏幕上,Shader有5个直接子类,分别是BitmapShader、ComposeShader、LinearGradient、RadialGradient以及SweepGradient。

10.1、BitmapShader(图像渲染)

   BitmapShader的作用是使用一张位图作为纹理来对某一区域进行填充。可以想象成在一块区域内铺瓷砖,只是这里的瓷砖是一张张位图而已。

BitmapShader函数原型为:

public BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY);

   其中,参数bitmap表示用来作为纹理填充的位图;参数tileX表示在位图X方向上位图衔接形式;参数tileY表示在位图Y方向上位图衔接形式。

Shader.TileMode有3种参数可供选择,分别为CLAMP、REPEAT和MIRROR。

1、CLAMP:的作用是如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色。

2、REPEAT:的作用是在横向和纵向上以平铺的形式重复渲染位图。

3、MIRROR:的作用是在横向和纵向上以镜像的方式重复渲染位图。

10.2、LinearGradient(线性渲染)

   LinearGradient的作用是实现某一区域内颜色的线性渐变效果。

LinearGradient的函数原型为:public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile);

   其中,参数x0表示渐变的起始点x坐标;参数y0表示渐变的起始点y坐标;参数x1表示渐变的终点x坐标;参数y1表示渐变的终点y坐标;参数colors表示渐变的颜色数组;参数positions用来指定颜色数组的相对位置;参数tile表示平铺方式,通常,参数positions设为null,表示颜色数组以斜坡线的形式均匀分布。


10.3、ComposeShader(混合渲染)

   ComposeShader的作用是实现渲染效果的叠加,如BitmapShader与LinearGradient的混合渲染效果等。

ComposeShader的函数原型为:public ComposeShader (Shader shaderA, Shader shaderB, PorterDuff.Mode mode);

   其中,参数shaderA表示某一种渲染效果;参数shaderB也表示某一种渲染效果;参数mode表示两种渲染效果的叠加模式,PorterDuff.Mode有16种参数可供选择,分别为:CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。


PorterDuff.Mode类型效果:


10.4、RadialGradient(环形渲染)

   RadialGradient的作用是在某一区域内实现环形的渐变效果。

   RadialGradient的函数原型为:public RadialGradient (float x, float y, float radius, int[] colors, float[] positions, Shader.TileMode tile);

   其中,参数x表示环形的圆心x坐标;参数y表示环形的圆心y坐标;参数radius表示环形的半径;参数colors表示环形渐变的颜色数组;参数positions用来指定颜色数组的相对位置;参数tile表示平铺的方式。


10.5、SweepGradient(梯度渲染)

   SweepGradient也称为扫描渲染,是指在某一中心以x轴正方向逆时针旋转一周而形成的扫描效果的渲染形式。

   SweepGradient的函数原型为:public SweepGradient (float cx, float cy, int[] colors, float[] positions);

   其中,参数cx表示扫描的中心x坐标;参数cy表示扫描的中心y坐标;参数colors表示梯度渐变的颜色数组;参数positions用来指定颜色数组的相对位置。


十一:Matrix类的详解

11.1、概述

   对绘图进行变换处理,需要借助Matrix类(android.graphics.Matrix)。

该类是一个矩阵工具类,本身不能对图像或View进行变换,但是可以结合Canvas的API来控制图形、View的变换。

Matrix提供的控制图片变换的方法主要包括:

1、setTranslate(float dx,float dy):控制Matrix进行位移。

2、setSkew(float kx,float ky):控制Matrix进行倾斜,kx、ky为X、Y方向上的比例。

3、setSkew(float kx,float ky,float px,float py):控制Matrix以px、py为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例。

4、setRotate(float degrees):控制Matrix进行depress角度的旋转,轴心为(0,0)。

5、setRotate(float degrees,float px,float py):控制Matrix进行depress角度的旋转,轴心为(px,py)。

6、setScale(float sx,float sy):设置Matrix进行缩放,sx、sy为X、Y方向上的缩放比例。

7、setScale(float sx,float sy,float px,float py):设置Matrix以(px,py)为轴心进行缩放,sx、sy为X、Y方向上的缩放比例。

概述:我们在自定义 View 控件时随处可见 Matrix 的身影,主要用于坐标转换映射,我们可以通过 Matrix 矩阵来控制视图的变换,Matrix 本质上是一个如下图所示的矩阵:


Matrix 提供了如下几个操作:

1、缩放(Scale)

对应 MSCALE_X 与 MSCALE_Y

2、位移(Translate)

对应 MTRANS_X 与 MTRANS_Y

3、错切(Skew)

对应 MSKEW_X 与 MSKEW_Y

4、旋转(Rotate)

旋转没有专门的数值来计算,Matrix 会通过计算缩放与错切来处理旋转。


11.2、几个注意点

一:对于一个从BitmapFactory.decodeXxx()方法加载的Bitmap对象而言,它是一个只读的,无法对其进行处理,必须使用Bitmap.createBitmap()方法重新创建一个Bitmap对象的拷贝,才可以对拷贝的Bitmap进行处理。

二:因为图像的变换是针对每一个像素点的,所以有些变换可能发生像素点的丢失,这里需要使用Paint.setAnitiAlias(boolean)设置来消除锯齿,这样图片变换后的效果会好很多。

三:在重新创建一个Bitmap对象的拷贝的时候,需要注意它的宽高,如果设置不妥,很可能变换后的像素点已经移动到”图片之外”去了。


11.3、关于Canvas和Matrix的一些理解

    可以将Canvas理解为坐标系。我们在View或者Bitmap上进行绘制时,都是绘制到屏幕或者Bitmap上,都是有限的大小,而Canvas可以理解为是无限大的,如果将Canvas理解为坐标系,那么Canvas就成了我们绘制内容的参考基准,比如,默认Canvas的圆点在绘制区域的左上角,如果对Canvas进行位移,效果如下:


   上图中,右侧红色区域就是偏移后的坐标系,使用偏移后的坐标系进行绘制,效果就是平移了。通过Matrix进行变换,那么Matrix就会保留变换之后的状态,如果多次变换,就会造成变换的累加。 为了不造成累加后的坐标混乱,Canvas提供了save()和restore()方法对Canvas的状态进行暂存和恢复。 在Canvas变换前,先使用save()方法,保存Canvas的状态,绘制完成之后,再使用restore()方法,将Canvas的状态恢复到save()的时候的状态,然后再进行下一次变换和绘制。 结合坐标系的理解,save()和restore()只是针对坐标系的变换而言,之前绘制的内容不会出现任何改动。

图解:

十二:自定义控件ProgressTimeBar源码分析

项目运行:(项目地址:https://github.com/Wiser-Wong/ProgressTimeBar

下面是本人看过源码后提出的问题,如果能回答上相关问题,对源码就会一定理解。

问题汇总:

1、源码中使用多个RectF的作用是什么?

2、源码中是如何兼容

app:progressUnPlaySrc="@drawable/progress_unplay_bg"

app:progressUnPlaySrc="@color/colorPrimary"相中样式的?

3、进度条的渐变色是如何实现的?

4、如何获取文字的绘制出来的宽度?

5、onMeasure函数是如何确定控件的宽高的?

6、onLayout函数是如何确定进度条各个子部分的位置(进度条、时间、进度按钮等)

7、onDraw函数如何绘制出整个控件的?(执行的函数,参数的设置)

8、canvas.save()与canvas.restore()的作用是什么?

9、如何判断端用户处在按下并滑动进度按钮的状态?


十三:自定义控件TagFlowLayout源码分析

项目运行:(项目地址:https://github.com/hongyangAndroid/FlowLayout备注:运行项目需要更新项目插件版本,改为:'com.novoda:bintray-release:0.8.0' )

问题汇总:

1、在源码中,当前控件的适配器应该如何设计?

2、在onMeasure中是如何确、确定控件的高度的?

3、控件中为什么需要四个数组存储控件的相关数据,为了解决什么问题?

4、控件的换行逻辑是怎么实现的?

5、onSaveInstanceState()与onRestoreInstanceState()函数在控件中是如何实现的,有什么作用?

6、控件没有实现onDraw方法,那是如何把数据显示在界面上的?

7、每个子控件的位置信息:top、buttom、left、right是如何确定的,调通了什么函数?

8、setAdapter()函数主要做了什么事情?

9、考虑一下,如果子控件满屏幕了,要如何实现滑动?

10、为什么容器控件setAdapter后,界面就可以刷新?


十四:自定义控件FishDrawable源码分析

项目运行:(项目地址:https://github.com/Jichensheng/Fish_2


问题汇总:

1、鱼的各个部位是如何绘制的,贝塞尔曲线是如何使用的?

2、点击界面鱼旋转身体再游过去是如何实现的?

3、为什么不直接继承View类,而是实现Drawable基类?

4、属性动画在该自定义控件的什么地方使用到?

5、为什么需要动画引擎,作用是什么?

6、鱼的不同部位的游动,实现思路是怎么样的,需要设计哪些方面的计算?


十五、如何优化自定义View

优化一:为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。

优化二:你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate(),没有参数的invalidate会强制重绘整个view。

优化三:另外一个非常耗时的操作是请求requestLayout()。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小,另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。

优化四:如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。

优化五:自定义控件需要考虑屏幕适配问题,设置一个基准值,在根据不用设备的宽高设置一个scale缩放比例,从而在不同的设备上实现一样的效果。


十六:扩展阅读

1、//www.greatytc.com/p/db01b37b6231(Android知识总结——Path常用方法解析)

2、http://double0291.github.io/2015/12/03/graphics/(Android绘图那些事——Canvas,Matrix,Shader)

3、https://zhuanlan.zhihu.com/p/198068786(Android自定义控件进阶02-Canvas之绘制图形,系列相关)

4、//www.greatytc.com/p/777b735a2c16(看完这篇还不会自定义 View ,我跪搓衣板)

5、//www.greatytc.com/p/29bb35a4860e(Android: 自定义View)

6、https://juejin.im/post/5e54e2de5188254945386529( Android自定义View,你摸的透透的了?)

7、//www.greatytc.com/p/46ffc75dcbe2(如何优化自定义View)

8、https://blog.csdn.net/weixin_34179762/article/details/89443251( Canvas 的基本原理)

9、//www.greatytc.com/p/356619fe64d5(让你的app提升一个档次-Android酷炫自定义控件)

10、https://www.cnblogs.com/coding-way/p/3595653.html(Android Path里FillType功能)

11、//www.greatytc.com/p/c52ceee36f70(Android自定义View——从零开始实现圆形进度条)

12、https://blog.csdn.net/harvic880925/article/details/51010839(Paint之函数大汇总)

13、https://blog.csdn.net/harvic880925/article/details/50995268(系列文章,非常全面)

14、GcsSloop的系列博客

15、//www.greatytc.com/p/3dd3d1524851#comments(自定义Drawable实现灵动的红鲤鱼动画)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343