view的自定义小结

作者: 芝林_e8cf | 来源:发表于2019-08-06 11:37 被阅读0次

    自定义View

    为什么要自定义View?

    ---既然goole已经为我们提供了很多原生的view,我们为什么还要自定义view呢?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。同时自定义View对于一个Android开发者来说是必须掌握的知识点,也是Android开发进阶的必经之路。

    如何实现view的自定义?

    自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw();

    View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。

    测量:onMeasure()决定View的大小;

    布局:onLayout()决定View在ViewGroup中的位置;

    绘制:onDraw()决定绘制这个View。

    自定义控件又分为自定义View和自定义ViewGroup,自定义View只需要重写onMeasure()和onDraw()即可,而自定义ViewGroup则只需要重写onMeasure()和onLayout()。

    自定义View原理是Android开发者必须了解的基础,基础掌握了我们才能进行下一步的学习

    基础的学习思路:

    View的分类

    类别解释特点

    单一视图即一个View,如TextView不包含子View

    视图组即多个View组成的ViewGroup,如LinearLayout包含子View

    View类简介:

              View类是Android中各种组件的基类,如View是ViewGroup基类。

              Android中的UI组件都由View、ViewGroup组成。

              View的构造函数:共有4个,具体如下:

    --------------自定义view必须重写至少一个构造

    // 如果View是在Java代码里面new的,则调用第一个构造函数

    publicCarsonView(Contextcontext) {

    super(context);

       }

    // 如果View是在.xml里声明的,则调用第二个构造函数

    // 自定义属性是从AttributeSet参数传进来的

    publicCarsonView(Contextcontext,AttributeSetattrs) {

    super(context,attrs);

       }

    // 不会自动调用

    // 一般是在第二个构造函数里主动调用

    // 如View有style属性时

    publicCarsonView(Contextcontext,AttributeSetattrs,intdefStyleAttr) {

    super(context,attrs,defStyleAttr);

       }

    //API21之后才使用

    // 不会自动调用

    // 一般是在第二个构造函数里主动调用

    // 如View有style属性时

    publicCarsonView(Contextcontext,AttributeSetattrs,intdefStyleAttr,intdefStyleRes) {

    super(context,attrs,defStyleAttr,defStyleRes);

       }

    3.View视图结构

    对于多View的视图,结构是树形结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View,如下图:

    一定要记住:无论是measure过程、layout过程还是draw过程,永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),最终计算整个View树中各个View,最终确定整个View树的相关属性。

    Android坐标系

    Android的坐标系定义为:

    屏幕的左上角为坐标原点

    向右为x轴增大方向

    向下为y轴增大方向

    View位置(坐标)描述

    View的位置由4个顶点决定的(如下A、B、C、D):

    注意:View的位置是相对于父控件而言的

    Top:子View上边界到父view上边界的距离

    Left:子View左边界到父view左边界的距离

    Bottom:子View下边距到父View上边界的距离

    Right:子View右边界到父view左边界的距离

    位置获取方式

    View的位置是通过view.getxxx()函数进行获取:(以Top为例)

    // 获取Top位置

    public final int getTop() {  

       return mTop;  

    }  

    // 其余如下:

      getLeft();      //获取子View左上角距父View左侧的距离

      getBottom();    //获取子View右下角距父View顶部的距离

      getRight();     //获取子View右下角距父View左侧的距离

    Android的角度(angle)与弧度(radian)

    自定义View实际上是将一些简单的形状通过计算,从而组合到一起形成的效果。

    这会涉及到画布的相关操作(旋转)、正余弦函数计算等,即会涉及到角度(angle)与弧度(radian)的相关知识

    角度和弧度都是描述角的一种度量单位,区别如下图:

    在默认的屏幕坐标系中角度增大方向为顺时针。

    注:在常见的数学坐标系中角度增大方向为逆时针

    Android中颜色相关内容:

      Android中的颜色相关内容包括颜色模式,创建颜色的方式,以及颜色的混合模式等。

      Android支持的颜色模式: 

    以ARGB8888为例介绍颜色定义:

    在java中定义颜色:

    //java中使用Color类定义颜色

    int color = Color.GRAY;     //灰色

    //Color类是使用ARGB值进行表示

    int color = Color.argb(127, 255, 0, 0);   //半透明红色

    int color = 0xaaff0000;                   //带有透明度的红色

    在xml文件中定义颜色:

    <?xml version="1.0" encoding="utf-8"?>

    <resources>

       //定义了红色(没有alpha(透明)通道)

       <color name="red">#ff0000</color>

       //定义了蓝色(没有alpha(透明)通道)

       <color name="green">#00ff00</color>

    </resources>

    在java文件中引用xml中定义的颜色:

    //方法1

    int color = getResources().getColor(R.color.mycolor);

    //方法2(API 23及以上)

    int color = getColor(R.color.myColor);

    在xml文件(layout或style)中引用或者创建颜色:

    <!--在style文件中引用-->

      <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

          <item name="colorPrimary">@color/red</item>

      </style>

    <!--在layout文件中引用在/res/values/color.xml中定义的颜色-->

    android:background="@color/red"    

    <!--在layout文件中创建并使用颜色-->

    android:background="#ff0000"

    有了以上的基础,接下来我们就可以来学习自定义view的具体过程了;

    首先是Measure过程

    OnMeasure()方法是自定义控件中非常重要的一个方法,下面我们来系统的学习,由浅至深来Measure的过程

    Measure的作用作用:

    测量View的宽/高:

    1.在某些情况下,需要多次测量(measure)才能确定View最终的宽/高;

    2.在这种情况下measure过程后得到的宽/高可能是不准确的;

    3.建议在layout过程中onLayout()去获取最终的宽/高

    准备的基础

    在了解measure 过程前,我们需要先了解measure过程中传递尺寸(宽 / 高测量值)的两个类:

    ViewGroup.LayoutParams (View 自身的布局参数)

    MeasureSpecs 类(父视图对子视图的测量要求)

    2.1  ViewGroup.LayoutParams:

    这个类我们很常见,用来指定视图的高度(height)和宽度(width)等布局参数。可通过以下参数进行指定:

    fill_parent :     即一个View,如TextView

    match_parent:    与fill_parent相同,用于Android 2.3及之后版本

    wrap_content :    自适应大小,强制性地使视图扩展以便显示其全部内容(含 padding )

    具体的应用如下图:

    android:layout_weight="wrap_content"   //自适应大小  

    android:layout_weight="match_parent"   //与父视图等高  

    android:layout_weight="fill_parent"    //与父视图等高  

    android:layout_weight="100dip"         //精确设置高度值为 100dip

    ViewGroup 的子类有其对应的 ViewGroup.LayoutParams 子类

    1.ViewGroup 的子类包括RelativeLayout、LinearLayout等;

    2.如 RelativeLayout的 ViewGroup.LayoutParams 的子类是RelativeLayoutParams。

    构造函数

    构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性。

    // View的构造函数有四种重载

       public DIY_View(Context context){

           super(context);

       }

       public DIY_View(Context context,AttributeSet attrs){

           super(context, attrs);

       }

       public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){

           super(context, attrs,defStyleAttr);

    // 第三个参数:默认Style

    // 默认Style:指在当前Application或Activity所用的Theme中的默认Style

    // 且只有在明确调用的时候

    2.2      MeasureSpec

    2.21  定义:测量规格也可理解为是测量View的依据

    MeasureSpec的类型分为两种:

    即每个MeasureSpec代表了一组宽度和高度的测量规格

    2.22  作用:决定了一个view的大小(宽/高)

    2.23  组成:如下图

    其中,Mode模式共分为三类:

    UNSPECIFIED模式():unspecified  (未指明的;未详细说明的)

    EXACTLY模式:exactly  (恰好地;正是;精确地;正确地)

    AT_MOST模式:at most:至多

    具体说明如下图:

    2.2.4  MeasureSpec类的使用

    MeasureSpec 、Mode 和Size都封装在View类中的一个内部类里 - MeasureSpec类。

    MeasureSpec类通过使用二进制,将mode和size打包成一个int值来减少对象内存分配,用一个变量携带两个数据(size,mode),并提供了打包和解包的方法

    2.2.6  MeasureSpec值的确定:

    子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里。

    如下图:

    关于getChildMeasureSpec()里对于子View的测量模式和大小的判断逻辑有点复杂;

    别担心,我已经帮大家总结好。具体子View的测量模式和大小请看下表:

    规律总结:(以子View为标准,横向观察)

    当子View采用具体数值(dp / px)时无论父容器的测量模式是什么,子View的测量模式都是EXACTLY且大小等于设置的具体数值;

    当子View采用match_parent时子View的测量模式与父容器的测量模式一致若测量模式为EXACTLY,则子View的大小为父容器的剩余空间;若测量模式为AT_MOST,则子View的大小不超过父容器的剩余空间

    当子View采用wrap_parent时无论父容器的测量模式是什么,子View的测量模式都是AT_MOST且大小不超过父容器的剩余空间。

    UNSPECIFIED模式:由于适用于系统内部多次measure情况,很少用到,故此处不讨论

    注:区别于顶级View(即DecorView)的计算逻辑

    onMeasure()方法中常用的方法:

    1.getChildCount():获取子View的数量;

    2.getChildAt(i):获取第i个子控件;

    3.subView.getLayoutParams().width/height:设置或获取子控件的宽或高;

    4.measureChild(child, widthMeasureSpec, heightMeasureSpec):测量子View的宽高;

    5.child.getMeasuredHeight/width():执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;

    6.getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;

    7.setMeasuredDimension(width, height):重新设置控件的宽高。如果写了这句代码,就需要删除“super. onMeasure(widthMeasureSpec, heightMeasureSpec);”这行代码。

    注意:onMeasure()方法可能被调用多次,这是因为控件中的内容或子View可能对分配给自己的空间“不满意”,因此向父空间申请重新分配空间。

    接下来是layout过程:

    主要说onLayout()这个方法:

    onLayout()方法负责布局,大多数情况是在自定义ViewGroup中才会重写,主要用来确定子View在这个布局空间中的摆放位置。 onLayout(boolean changed, int left, int top, int right, int bottom)方法有5个参数,其中changed表示这个控件是否有了新的尺寸或位置;left、top、right、bottom分别表示这个View相对于父布局的左/上/右/下方的位置。

    以下是onLayout()方法中常用的方法:

    1.getChildCount():获取子View的数量;

    2.getChildAt(i):获取第i个子View

    3.getWidth/Height():获取onMeasure()中返回的宽度和高度的测量值;

    4.child.getLayoutParams():获取到子View的LayoutParams对象;

    5.child.getMeasuredWidth/Height():获取onMeasure()方法中测量的子View的宽度和高度值;

    6.getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;

    7.child.layout(l, t, r, b):设置子View布局的上下左右边的坐标。

    Draw过程:

    onDraw()  方法负责绘制,即如果我们希望得到的效果在Android原生控件中没有现成的支持,那么我们就需要自己绘制我们的自定义控件的显示效果。要学习onDraw()方法,我们就需要学习在onDraw()方法中使用最多的两个类:PaintCanvas

    注意:每次自定义View/ViewGroup时都会调用onDraw()方法。

    Paint类

    Paint 画笔对象,这个类中包含了如何绘制几何图形、文字和位图的样式和颜色信息,指定了如何绘制文本和图形。画笔对象有很多设置方法,大体上可以分为两类:图形绘制和文本绘制。

    Paint类中有如下方法:

    1、图形绘制:

    1)  setArgb(int a, int r, int g, int b):设置绘制的颜色,a表示透明度,r、g、b表示颜色值;

    2)  setAlpha(int a):设置绘制的图形的透明度;

    3)  setColor(int color):设置绘制的颜色;

    4)  setAntiAlias(boolean a):设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢;

    5)  setDither(boolean b):设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰;

    6)  setFileterBitmap(Boolean b):设置是否在动画中滤掉Bitmap的优化,可以加快显示速度;

    7)  setMaskFilter(MaskFilter mf):设置MaskFilter来实现滤镜的效果;

    8)  setColorFilter(ColorFilter cf):设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果;

    9)  setPathEffect(PathEffect pe):设置绘制的路径的效果;

    10) setShader(Shader s):设置Shader绘制各种渐变效果;

    11) setShadowLayer(float r, int x, int y, int c):在图形下面设置阴影层,r为阴影角度,x和y为阴影在     x轴和y轴上的距离,c为阴影的颜色;

    12) setStyle(Paint.Style s):设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心;

    13) setStrokeCap(Paint.Cap c):当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;

    14) setStrokeJoin(Paint.Join j):设置绘制时各图形的结合方式;

    15) setStrokeWidth(float w):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;

    16) setXfermode(Xfermode m):设置图形重叠时的处理方式;

    2、文本绘制:

    1)  setTextAlign(Path.Align a):设置绘制的文本的对齐方式;

    2)  setTextScaleX(float s):设置文本在X轴的缩放比例,可以实现文字的拉伸效果;

    3)  setTextSize(float s):设置字号;

    4)  setTextSkewX(float s):设置斜体文字,s是文字倾斜度;

    5)  setTypeFace(TypeFace tf):设置字体风格,包括粗体、斜体等;

    6)  setUnderlineText(boolean b):设置绘制的文本是否带有下划线效果;

    7)  setStrikeThruText(boolean b):设置绘制的文本是否带有删除线效果;

    8)  setFakeBoldText(boolean b):模拟实现粗体文字,如果设置在小字体上效果会非常差;

    9)  setSubpixelText(boolean b):如果设置为true则有助于文本在LCD屏幕上显示效果;

    Canvas类

    Canvas  即画布,其上可以使用Paint画笔对象绘制很多东西。Canvas对象中可以绘制:

    1)  drawArc():绘制圆弧;

    2)  drawBitmap():绘制Bitmap图像;

    3)  drawCircle():绘制圆圈;

    4)  drawLine():绘制线条;

    5)  drawOval():绘制椭圆;

    6)  drawPath():绘制Path路径;

    7)  drawPicture():绘制Picture图片;

    8)  drawRect():绘制矩形;

    9)  drawRoundRect():绘制圆角矩形;

    10) drawText():绘制文本;

    11) drawVertices():绘制顶点。

    Canvas对象的其他方法:

    1)  canvas.save():把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制;

    2)  canvas.restore():把当前画布调整到上一个save()之前的状态;

    3)  canvas.translate(dx, dy):把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照;

    4)  canvas.scale(x, y):将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍;

    5)  canvas.rotate(angle):将当前画布顺时针旋转angle度.

    小结:绘制什么,由Canvas处理。          怎么去绘制,由Paint处理。

    最后我们需要注意的是绘制顺序

    Android 里面的绘制都是按顺序的,先绘制的内容会被后绘制的盖住。比如你在重叠的位置先画圆再画方,和先画方再画圆所呈现出来的结果肯定是不同的:

    到底放在 super.onDraw() 上面还是下面?通常如果我们继承的是 View 的话,super.onDraw() 只是一个空实现,所以它的位置放在哪儿都没事,甚至直接不要也没事,但反正加上也没啥影响,尽量还是加上吧。由于 Android 的绘制顺序性,当你继承至已经有绘制的其他 View(比如 TextView)的时候,放在 super.onDraw() 上面就意味着绘制代码会被控件的原内容盖住。

    dispatchDraw():绘制子 View 的方法

    自定义绘制其实不止 onDraw() 一个方法。onDraw() 只是负责自身主体内容绘制的。而有的时候,你想要的遮盖关系无法通过 onDraw() 来实现,而是需要通过别的绘制方法。

    例如,你继承了一个 LinearLayout,重写了它的 onDraw() 方法,在 super.onDraw() 中插入了你自己的绘制代码,使它能够在内部绘制一些斑点作为点缀:

    看起来确实没有问题,但是你会发现,当你添加了子 View 之后,你的斑点不见了:

    造成这种情况的原因是 Android 的绘制顺序:在绘制过程中,每一个 ViewGroup 会先调用自己的 onDraw() 来绘制完自己的主体之后再去绘制它的子 View。对于上面这个例子来说,就是你的 LinearLayout 会在绘制完斑点后再去绘制它的子 View。那么在子 View 绘制完成之后,先前绘制的斑点就被子 View 盖住了。具体来讲,这里说的「绘制子 View」是通过另一个绘制方法的调用来发生的,这个绘制方法叫做:dispatchDraw()。也就是说,在绘制过程中,每个 View 和 ViewGroup 都会先调用 onDraw() 方法来绘制主体,再调用 dispatchDraw() 方法来绘制子 View。

    怎样才能让 LinearLayout 的绘制内容盖住子 View 呢?只要让它的绘制代码在子 View 的绘制之后再执行就好了。所以直接执行在 super.dispatchDraw() 的下面即可。

    下面是绘制的流程图:

    view自定义直通车

    相关文章

      网友评论

        本文标题:view的自定义小结

        本文链接:https://www.haomeiwen.com/subject/zewplctx.html