android自定义view案例,Android自定义View,你摸的透透的了?

本文讲解了自定义View的测量方法,包括MeasureSpec的使用,以及如何避免过度绘制进行性能优化。内容涉及View的测量流程,坐标系转换,onDraw方法的应用,以及如何通过减少嵌套和去除默认背景提升绘制效率。
摘要由CSDN通过智能技术生成

03a3d8354ca2c19df713f9cec8bb8006.png

前言

View,有很多的名称。不论是你熟知的布局,还是控件,他们全部都继承自View。cf173e09ff88b752963b5f93aeafe490.png

思维导图

866f93549e02683d7cb2276179fa0f6e.png

工作流程

measure

其实通过layout中的第二张图我们已经知道了控件大小的计算了。

height = bottom - top

width = right - left

对于ViewGroup而言,就是对容器内子控件的遍历和计算了。

因为直接继承自View的控件使用wrap_cotent和match_parent是显示出来的效果是相同的。需要我们使用MeasureSpec中的getMode()方法来对当前的模式进行区分和比较。

模式

状态

UNSPECIFIED

未指定模式,View想多大就多大,父容器不做限制,一般用于系统内部的测量

AT_MOST

最大模式,对应wrap_content,View的大小不大于SpecSize的值

EXACTLY

精确模式,对应match_parent,View的大小为SpecSize的值

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//用于获取设定的模式

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

// 用于获取设定的长度

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

// 类似这样的判断,后面不过多复述

// 用于判断是不是wrap_content

// 如果不进行处理,效果会是match_parent

if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){

setMeasuredDimension(20, 20);

}

}

复制代码

layout

在确定位置时,我们有一个非常需要主要的地方—— 坐标系。Android系统的坐标系和平时画的坐标系并不相同。d085d3b5b7ec35a8fcbcf4a6b90e2d78.png

所以相对应的,我们的位置计算方法自然和我们原来的正好是相反的。03516f07c034d492abb8bad5c773f762.png

4个顶点的位置分别由4个值决定:

top:子View上边界到所在容器上边界的距离。

left:子View左边界到所在容器左边界的距离。

bottom:子View下边界到所在容器上边界的距离。

right:子View右边界到所在容器左边界的距离。

所有的计算都是相对于所在容器才能够开始的。

draw

一共有6个步骤:

如果需要,则绘制背景 -- drawBackground(canvas);

保存当前canvas层 -- saveCount = canvas.getSaveCount();

绘制View的内容 -- if (!dirtyOpaque) onDraw(canvas);

绘制子View -- dispatchDraw(canvas);

如果需要,则绘制View的褪色边缘,类似于阴影效果 -- canvas.restoreToCount(saveCount);

绘制装饰,比如滚动条 -- onDrawForeground(canvas);

关于开发者需要重写的方法一般是第三步绘制View的内容对应的onDraw()。

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

@Override

protected void onDraw(Canvas canvas){

super.onDraw(canvas);

int width = getWidth();

int height = getHeight();

// 在画布上进行类似这样的操作

canvas.drawLine(0, height/2, width,height/2, paint);

}

复制代码

入门自定义View

在日常项目的布局文件中我们经常会使用到xmlns:app="http://schemas.android.com/apk/res-auto"这样标签,其实他就是用来引入我们自定义的标签使用的。

在res/values目录下创建attrs

复制代码在DefaultView(Context context, @Nullable AttributeSet attrs)中获取。以下是整个完整代码。

/**

* author: ClericYi

* time: 2020-01-30

*/

public class DefaultView extends View{

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

private int mColor = Color.RED;

public DefaultView(Context context, @Nullable AttributeSet attrs){

super(context, attrs);

initAttrs(context, attrs);

initDraw();

}

private void initAttrs(Context context, @Nullable AttributeSet attrs){

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefaultView);

// 从styleable中获取的名字是系统会生成的,一般是 类名_name 的形式

mColor = array.getColor(R.styleable.DefaultView_color, Color.GREEN);

// 获取完资源后即使回收

array.recycle();

}

private void initDraw(){

paint.setColor(mColor);

paint.setStrokeWidth(3f);

}

@Override

protected void onDraw(Canvas canvas){

super.onDraw(canvas);

int width = getWidth();

int height = getHeight();

canvas.drawLine(0, height/2, width,height/2, paint);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

if(widthMode == MeasureSpec.AT_MOST){

setMeasuredDimension(20, 20);

}

}

}

复制代码

基础的性能优化

首先的话我们先了解如何去知道一个View是否被过度绘制了?

其实在我们手机中的开发模式已经存在这个选项了。

开启前

开启后

397ac6a45ce760c8c9f0c49507ad736b.png

1f14e40b56ab5d75226fa36b61e5f6e9.png

下方给出绘制的次数对应图8593699b963b1dbed73d507f74b8ad06.png

那如何做到性能优化呢?

在这个问题之前,需要了解什么是过度绘制,你可以理解为同一位置的控件不断的叠加而产生的无用数据,那我们就来说说集中解决方案吧。

方案1: 减少嵌套层数。

使用线性布局

使用约束布局

a178b506f17829376c3d8cf023dde893.png

42184a3636126f52603a6325f084920a.png

因为只是一个案例,想说的意思,如果多个LinearLayout嵌套实现的效果,如果能被一个ConstraintLayout直接实现,那么就用后者替代,因为不会这样在同一个区域重复出现

方案2: 去除默认的背景

701dafe6a60a62e6eab01b7670338938.png

这个解决方案其实针对的背景会被自动绘制的问题,如果我们把这个层次消去,从绘制角度老说也是一种提升了。正如图示一般直接减少了一层的绘制。

在代码中的具体表现,通过对style.xml中的Theme进行修改:

@null

复制代码

总结

ac4c2173ff96409ce7c6175d1931c6bc.png

以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值