Android自定义View,你摸的透透的了?

前言

View,有不少的名称。不管是你熟知的布局,仍是控件,他们所有都继承自Viewphp

View部分继承图

文内部分图片转载自Carson_Ho大佬的文章java

思惟导图

工做流程

measure

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

  • height = bottom - top
  • width = right - left

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

由于直接继承自View的控件使用wrap_cotentmatch_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系统的坐标系和平时画的坐标系并不相同。 app

Android屏幕坐标轴

因此相对应的,咱们的位置计算方法天然和咱们原来的正好是相反的。 ide

View位置计算

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

  • top:子View上边界到所在容器上边界的距离。
  • left:子View左边界到所在容器左边界的距离。
  • bottom:子View下边界到所在容器上边界的距离。
  • right:子View右边界到所在容器左边界的距离。

全部的计算都是相对于所在容器才可以开始的。post

draw

一共有6个步骤:性能

  1. 若是须要,则绘制背景 -- drawBackground(canvas);
  2. 保存当前canvas层 -- saveCount = canvas.getSaveCount();
  3. 绘制View的内容 -- if (!dirtyOpaque) onDraw(canvas);
  4. 绘制子View -- dispatchDraw(canvas);
  5. 若是须要,则绘制View的褪色边缘,相似于阴影效果 -- canvas.restoreToCount(saveCount);
  6. 绘制装饰,好比滚动条 -- 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"这样标签,其实他就是用来引入咱们自定义的标签使用的。

  1. res/values目录下建立attrs
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DefaultView">
        <attr name="color" format="color"/>
    </declare-styleable>
</resources>
复制代码
  1. 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是否被过分绘制了?

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

开启前 开启后

下方给出绘制的次数对应图

那如何作到性能优化呢?

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

方案1: 减小嵌套层数。

使用线性布局 使用约束布局

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

方案2: 去除默认的背景

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

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

<item name="android:windowBackground">@null</item>
复制代码

总结

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


相关文章推荐:

应用层中除了HTTP,你还知道点啥?

TCP层的那些知识,你掌握了多少?

Android必知必会的四大组件 -- ContentProvider篇

相关文章
相关标签/搜索