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();}
@Override
protected 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);
}
}
@Override
protected 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”。
程序运行后果如下所示;
图片