当前位置:主页 > 查看内容

Android自定义View,敢说都知道吗?

发布时间:2021-06-17 00:00| 位朋友查看

简介:前言 View,有很多的名称。不论是你熟知的布局,还是控件,他们全部都继承自View。 文内部分图片转载自Carson_Ho大佬的文章 思维导图 工作流程 measure 其实通过layout中的第二张图我们已经知道了控件大小的计算了。 height = bottom - top width = right -……

 前言

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

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

思维导图

工作流程

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的值
  1. @Override 
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  3.         super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  4.         //用于获取设定的模式 
  5.         int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
  6.         int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
  7.  
  8.         // 用于获取设定的长度 
  9.         int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  10.         int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
  11.  
  12.         // 类似这样的判断,后面不过多复述 
  13.         // 用于判断是不是wrap_content 
  14.         // 如果不进行处理,效果会是match_parent 
  15.         if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){ 
  16.             setMeasuredDimension(20, 20); 
  17.         } 

layout

在确定位置时,我们有一个非常需要主要的地方—— 坐标系。

Android系统的坐标系和平时画的坐标系并不相同。

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

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

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

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

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()。

  1. private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 
  2.     @Override 
  3.     protected void onDraw(Canvas canvas) { 
  4.         super.onDraw(canvas); 
  5.         int width = getWidth(); 
  6.         int height = getHeight(); 
  7.         // 在画布上进行类似这样的操作 
  8.         canvas.drawLine(0, height/2, width,height/2, paint); 
  9.     } 

入门自定义View

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

1.在res/values目录下创建attrs

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <resources> 
  3.     <declare-styleable name="DefaultView"
  4.         <attr name="color" format="color"/> 
  5.     </declare-styleable> 
  6. </resources> 

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

  1. /** 
  2.  * author: ClericYi 
  3.  * time: 2020-01-30 
  4.  */ 
  5. public class DefaultView extends View { 
  6.     private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 
  7.     private int mColor = Color.RED; 
  8.  
  9.     public DefaultView(Context context, @Nullable AttributeSet attrs) { 
  10.         super(context, attrs); 
  11.         initAttrs(context, attrs); 
  12.         initDraw(); 
  13.     } 
  14.  
  15.     private void initAttrs(Context context, @Nullable AttributeSet attrs) { 
  16.         TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefaultView); 
  17.         // 从styleable中获取的名字是系统会生成的,一般是 类名_name 的形式 
  18.         mColor = array.getColor(R.styleable.DefaultView_color, Color.GREEN); 
  19.         // 获取完资源后即使回收 
  20.         array.recycle(); 
  21.     } 
  22.  
  23.     private void initDraw() { 
  24.         paint.setColor(mColor); 
  25.         paint.setStrokeWidth(3f); 
  26.     } 
  27.  
  28.     @Override 
  29.     protected void onDraw(Canvas canvas) { 
  30.         super.onDraw(canvas); 
  31.         int width = getWidth(); 
  32.         int height = getHeight(); 
  33.         canvas.drawLine(0, height/2, width,height/2, paint); 
  34.     } 
  35.  
  36.     @Override 
  37.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  38.         super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  39.         int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
  40.         int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
  41.  
  42.         int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  43.         int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
  44.  
  45.         if(widthMode == MeasureSpec.AT_MOST){ 
  46.             setMeasuredDimension(20, 20); 
  47.         } 
  48.     } 

基础的性能优化

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

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


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

那如何做到性能优化呢?

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

方案1:减少嵌套层数。

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

方案2:去除默认的背景

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

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

  1. <item name="android:windowBackground">@null</item> 

总结


本文转载自网络,原文链接:https://juejin.im/post/5e54e2de5188254945386529
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:终于!iPhone能用安卓系统了 下一篇:没有了

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐