Android自定义View

转自:https://www.jianshu.com/p/e9d8420b1b9c

自定义View是Android开发者必须了解的基础

今天,我将手把手教你写一个自定义View,并理清自定义View所有应该的注意点

阅读本文前,请先阅读我写的一系列自定义View文章

(1)自定义View基础 - 最易懂的自定义View原理系列

(2)自定义View Measure过程 - 最易懂的自定义View原理系列

(3)自定义View Layout过程 - 最易懂的自定义View原理系列

(4)自定义View Draw过程- 最易懂的自定义View原理系列

Android事件分发机制详解:史上最全面、最易懂

1. 自定义View的分类

自定义View一共分为两大类,具体如下图:

分类

2. 具体介绍 & 使用场景

对于自定义View的类型介绍及使用场景如下图:

具体介绍 & 使用场景

3. 使用注意点

在使用自定义View时有很多注意点(坑),希望大家要非常留意:

使用注意点

3.1 支持特殊属性

支持wrap_content

如果不在onMeasure()中对wrap_content作特殊处理,那么wrap_content属性将失效

具体原因请看文章:为什么你的自定义View wrap_content不起作用?

支持padding & margin

如果不支持,那么padding和margin(ViewGroup情况)的属性将失效

对于继承View的控件,padding是在draw()中处理

对于继承ViewGroup的控件,padding和margin会直接影响measure和layout过程

3.2 多线程应直接使用post方式

View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。

3.3 避免内存泄露

主要针对View中含有线程或动画的情况:当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题

启动或停止线程/ 动画的方式:

启动线程/ 动画:使用view.onAttachedToWindow(),因为该方法调用的时机是当包含View的Activity启动的时刻

停止线程/ 动画:使用view.onDetachedFromWindow(),因为该方法调用的时机是当包含View的Activity退出或当前View被remove的时刻

3.4 处理好滑动冲突

当View带有滑动嵌套情况时,必须要处理好滑动冲突,否则会严重影响View的显示效果。

4. 具体实例

接下来,我将用自定义View中最常用的继承View来说明自定义View的具体应用和需要注意的点

4.1 继承VIew的介绍

Paste_Image.png

在下面的例子中,我将讲解:

如何实现一个基本的自定义View(继承VIew)

如何自身支持wrap_content & padding属性

如何为自定义View提供自定义属性(如颜色等等)

实例说明:画一个实心圆

4.2 具体步骤

创建自定义View类(继承View类)

布局文件添加自定义View组件

注意点设置(支持wrap_content & padding属性自定义属性等等)

下面我将逐个步骤进行说明:

步骤1:创建自定义View类(继承View类)

CircleView.java

// 用于绘制自定义View的具体内容// 具体绘制是在复写的onDraw()内实现publicclassCircleViewextendsView{// 设置画笔变量Paint mPaint1;// 自定义View有四个构造函数// 如果View是在Java代码里面new的,则调用第一个构造函数publicCircleView(Context context){super(context);// 在构造函数里初始化画笔的操作init();    }// 如果View是在.xml里声明的,则调用第二个构造函数// 自定义属性是从AttributeSet参数传进来的publicCircleView(Context context,AttributeSet attrs){super(context, attrs);        init();    }// 不会自动调用// 一般是在第二个构造函数里主动调用// 如View有style属性时publicCircleView(Context context,AttributeSet attrs,intdefStyleAttr ){super(context, attrs,defStyleAttr);        init();    }//API21之后才使用// 不会自动调用// 一般是在第二个构造函数里主动调用// 如View有style属性时publicCircleView(Context context, AttributeSet attrs,intdefStyleAttr,intdefStyleRes){super(context, attrs, defStyleAttr, defStyleRes);    }// 画笔初始化privatevoidinit(){// 创建画笔mPaint1 =newPaint ();// 设置画笔颜色为蓝色mPaint1.setColor(Color.BLUE);// 设置画笔宽度为10pxmPaint1.setStrokeWidth(5f);//设置画笔模式为填充mPaint1.setStyle(Paint.Style.FILL);    }// 复写onDraw()进行绘制  @OverrideprotectedvoidonDraw(Canvas canvas){super.onDraw(canvas);// 获取控件的高度和宽度intwidth = getWidth();intheight = getHeight();// 设置圆的半径 = 宽,高最小值的2分之1intr = Math.min(width, height)/2;// 画出圆(蓝色)// 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1canvas.drawCircle(width/2,height/2,r,mPaint1);    }}

特别注意:

View的构造函数一共有4个,具体使用请看:深入理解View的构造函数

理解View的构造函数

对于绘制内容为何在复写onDraw()里实现,具体请看我写的文章:自定义View Draw过程- 最易懂的自定义View原理系列(4)

步骤2:在布局文件中添加自定义View类的组件

activity_main.xml

步骤3:在MainActivity类设置显示

MainActivity.java

publicclassMainActivityextendsAppCompatActivity{@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}

效果图

好了,至此,一个基本的自定义View已经实现了。接下来继续看自定义View所有应该注意的点:

如何手动支持wrap_content属性

如何手动支持padding属性

如何为自定义View提供自定义属性(如颜色等等)

a. 手动支持wrap_content属性

先来看wrap_content & match_parent属性的区别

// 视图的宽和高被设定成刚好适应视图内容的最小尺寸android:layout_width="wrap_content"// 视图的宽和高延伸至充满整个父布局android:layout_width="match_parent"// 在Android API 8之前叫作"fill_parent"

如果不手动设置支持wrap_content属性,那么wrap_content属性是不会生效(显示效果同match_parent)

具体原因 & 解决方案请看我写的文章:为什么你的自定义View wrap_content不起作用?

b. 支持padding属性

padding属性:用于设置控件内容相对控件边缘的边距;

区别与margin属性(同样称为:边距):控件边缘相对父控件的边距(父控件控制),具体区别如下:

Paste_Image.png

如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的。

解决方案

绘制时考虑传入的padding属性值(四个方向)。

在自定义View类的复写onDraw()进行设置

CircleView.java

// 仅看复写的onDraw()@OverrideprotectedvoidonDraw(Canvas canvas){super.onDraw(canvas);// 获取传入的padding值finalintpaddingLeft = getPaddingLeft();finalintpaddingRight = getPaddingRight();finalintpaddingTop = getPaddingTop();finalintpaddingBottom = getPaddingBottom();// 获取绘制内容的高度和宽度(考虑了四个方向的padding值)intwidth = getWidth() - paddingLeft - paddingRight ;intheight = getHeight() - paddingTop - paddingBottom ;// 设置圆的半径 = 宽,高最小值的2分之1intr = Math.min(width, height)/2;// 画出圆(蓝色)// 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,r,mPaint1);    }

效果图

c. 提供自定义属性

系统自带属性,如

// 基本是以android开头android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"android:padding="30dp"

但有些时候需要一些系统所没有的属性,称为自定义属性

使用步骤有如下:

在values目录下创建自定义属性的xml文件

在自定义View的构造方法中解析自定义属性的值

在布局文件中使用自定义属性

下面我将对每个步骤进行具体介绍

步骤1:在values目录下创建自定义属性的xml文件

attrs_circle_view.xml

对于自定义属性类型 & 格式如下:

<--1.reference:使用某一资源ID-->// 使用格式  // 1. Java代码  private int ResID;  private Drawable ResDraw;  ResID = typedArray.getResourceId(R.styleable.SuperEditText_background, R.drawable.background); // 获得资源ID  ResDraw = getResources().getDrawable(ResID); // 获得Drawble对象  // 2. xml代码<--2.color:颜色值-->// 格式使用<--3.boolean:布尔值-->// 格式使用<--4.dimension:尺寸值-->// 格式使用:<--5.float:浮点值-->// 格式使用<--6.integer:整型值-->// 格式使用<--7.string:字符串-->// 格式使用<--8.fraction:百分数-->// 格式使用<--9.enum:枚举值-->// 格式使用<--10.flag:位或运算-->、// 使用<--特别注意:属性定义时可以指定多种类型值-->// 使用

步骤2:在自定义View的构造方法中解析自定义属性的值

此处是需要解析circle_color属性的值

// 该构造函数需要重写publicCircleView(Context context, AttributeSet attrs){this(context, attrs,0);// 原来是:super(context,attrs);init();publicCircleView(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);// 加载自定义属性集合CircleViewTypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView);// 解析集合中的属性circle_color属性// 该属性的id为:R.styleable.CircleView_circle_color// 将解析的属性传入到画圆的画笔颜色变量当中(本质上是自定义画圆画笔的颜色)// 第二个参数是默认设置颜色(即无指定circle_color情况下使用)mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);// 解析后释放资源a.recycle();        init();

步骤3:在布局文件中使用自定义属性

activity_main.xml

xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="scut.carson_ho.diy_view.MainActivity"    >app:circle_color="#FF4081"        />

Paste_Image.png

至此,一个较为规范的自定义View已经完成了。

完整代码下载

Carson_Ho的github:自定义View的具体应用

5. 总结

本文对自定义View的具体应用和注意点进行了全面分析

如果希望继续了解自定义View的原理,请参考我写的文章:

(1)自定义View基础 - 最易懂的自定义View原理系列

(2)自定义View Measure过程 - 最易懂的自定义View原理系列

(3)自定义View Layout过程 - 最易懂的自定义View原理系列

(4)自定义View Draw过程- 最易懂的自定义View原理系列

Android事件分发机制详解:史上最全面、最易懂

Canvas类的最全面详解 - 自定义View应用系列

Path类的最全面详解 - 自定义View应用系列

作者:Carson_Ho

链接:https://www.jianshu.com/p/e9d8420b1b9c

推荐阅读更多精彩内容

  • 【Android 自定义View】 [TOC] 自定义View基础 接触到一个类,你不太了解他,如果贸然翻阅源码只...
    Rtia阅读 1,723评论 1 10
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 137,949评论 20 590
  • 自定义View的有好几种分类,可以分成4种:1.特定的View的子类:Android的API已经为我们提供了不少可...
    StChris阅读 409评论 0 7
  • 通过前面的《View的事件体系》以及《View的工作原理》,对于自定义View已经有了比较充分的了解,接下来就可以...
    聽媽媽的话阅读 87评论 0 1
  • 可能因芒果粉的缘故,对长沙格外有亲近感。当水蓝儿说她来自长沙时,距离一下就把我们拉近了。于是我们的交流就从芒果台的...
    补拙莫如勤LV阅读 82评论 2 3