PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

ps:本文是基于 Android API 31 来剖析的,文章写的 demo 是用 AndroidStudio 工具来开发的。

目录

1、自定义 View 的分类

2、自定义 View 的注意事项

3、自定义 View 的实例

1、自定义 View 的分类

自定义 View 的分类到目前还没有对立的规范,以我对自定义 View 的学习积攒,我把自定义 View 布局为以下这4类。

(1)间接继承于 View

间接继承于 View 来绘制一些不规则的视图,须要通过绘制的形式来实现,也就是要重写 View 的 onDraw办法;间接继承于 View 的子类都是须要本人去解决 wrap_content (看Android中View的工作流程之measure过程这篇文章,如果自定义的 View 间接继承 View 不解决 wrap_content,那么就和 match_parent 的成果是一样的)和 padding ,否则做进去的成果和咱们实现的不一样。

(2)间接继承于 ViewGroup

这种办法用于实现自定义的布局,像 LinearLayout、RelativeLayout 等是属于零碎的布局,如果咱们须要从新定义一种新布局,能够采纳这种办法来实现,比如说布局能够左右(把ViewPager看作是一种自定义布局嘛)且还产生水波纹成果的时候,用这种形式有点简单,须要适当地解决 ViewGroup 的测量、布局这两个过程,与此同时解决子元素的测量和布局过程。

(3)间接继承于具体的 View(例如 TextView)

用于拓展某种已具备的 View 性能,比如说 TextView,它被 Button 继承着,Button 次要用来设置点击事件的;间接继承于具体的 View,它不须要本人去解决 wrap_content 和 padding。

(4)间接继承于具体的 ViewGroup(例如 FrameLayout)

如果某种成果看起来和具体的 ViewGroup 很像的时候,也就是和具体的 ViewGroup 布局子元素个性很像的时候,能够采纳这种办法来实现,采纳这种办法不须要本人解决 ViewGroup的 measure 和 layout 这两个过程。

一种 View 的显示成果有很多种自定义 View 的形式去实现,喜爱用哪种最终还是看开发者本人的爱好,好,咱们当初说一下自定义 View 应该留神的一些事项。

2、自定义 View 的注意事项

(1)间接继承于 View 或者 ViewGroup,当用到 wrap_content 值时,必须反对 wrap_content;如果不在 onMeasure 中对 wrap_content 做非凡解决,那么当自定义 View 或者自定义 ViewGroup 在布局中应用 wrap_content 时就没法达到预期的成果,那么咱们看到的就是和 match_parent 一样的成果,能够看Android中View的工作流程之measure过程这篇文章,你们就能够看到和 match_parent 一样的成果了。

(2)如果间接继承 View 且应用到 padding 时,如果不在 draw 办法中解决 padding,那么 padding 属性就不会起作用;如果间接继承自 ViewGroup 并且应用到 wrap_content 时,须要在 onMeasure 和 onLayout 办法中解决 padding 和子 View 的 margin 对它造成的影响。

(3)不要在自定义 View 中创立一个 Handler ,因为 View 提供了 post 一系列的办法,View 的一系列 post 办法其实还是调用了 Handler 的 post 一系列的办法。

(4)当 View 变得不可见时咱们须要进行线程和动画,如果不解决这种状况,很容易造成内存透露;如果有线程或者动画须要进行时,最好是在 View 的 onDetachedFromWindow 办法中进行调用,因为 Activity 退出或者 View 被删除的时候,View 的 onDetachedFromWindow 办法会被执行。

(5)如果 View 存在滑动抵触,该当解决好滑动抵触,否则会影响用户体验成果;无关滑动抵触的内容能够看Android中View的滑动抵触这篇文章。

3、自定义 View 的实例

咱们这里就写一个间接持续于 View 的案例,咱们就画一个椭圆,步骤如下所示;

(1)写一个 EllipseView并继承于 View;

package com.xe.myapplication4;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;

/**

  • Created by 86188 on 2022/7/7.
    */

public class EllipseView extends View {

Paint p;private int color;public EllipseView(Context context, @Nullable AttributeSet attrs) {    super(context, attrs);    init(context,attrs);}public EllipseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init(context,attrs);}private void init(Context context, @Nullable AttributeSet attrs) {    p = new Paint();    TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.EllipseView);    //1、    color = ta.getColor(R.styleable.EllipseView_ellipse_view_color,Color.GREEN);    ta.recycle();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);    //2、    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {        setMeasuredDimension(200,150);        //3、    } else if (widthSpecMode == MeasureSpec.AT_MOST) {        setMeasuredDimension(200,heightSpecSize);        //4、    } else if (heightSpecMode == MeasureSpec.AT_MOST) {        setMeasuredDimension(widthSpecSize,150);    }}@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //5、    int leftPadding = getPaddingLeft();    int rightPadding = getPaddingRight();    int topPadding = getPaddingTop();    int buttomPadding = getPaddingBottom();    int width = getMeasuredWidth();    int height = getMeasuredHeight();    int width2 = width  - rightPadding;    int height2 = height  - buttomPadding;    p.setColor(color);    p.setAntiAlias(true);    RectF rectF = new RectF();    rectF.set(leftPadding,topPadding,width2,height2);    canvas.drawOval(rectF, p);}

}

(2)在 values 目录下创立一个 attrs.xml 文件,并自定义一个 ellipse_view_color属性,格局为 color 类型的,其值就是色彩值;

图片

图片

(3)自定义 View 在 xml 中的布局;

图片

阐明:

1)看正文1,它是解析自定义属性的 ellipse_view_color 值,如果在 xml 布局文件中没有应用该属性,那么就默认为绿色。

2)看正文2、3、4,解决 wrap_content 模式,当宽在 wrap_content 模式时,就将宽设置为200;当高在 wrap_content模式时,就将高设置为150。

3)看正文5,获取上下左右的 padding,绘制自定义 View 时解决 padding。

4)看正文6的 ellipse_view_color 属性,是不是有 app 的前缀?这是自定义属性的标记,应用自定义属性时必须在 xml 布局文件中增加 schemas 申明:xmlns:app="http://schemas.android.com/apk/res-auto"。

程序运行后果如下所示;

图片