自定义View
判断自己有没有掌握这个知识点,就模拟面试,看看你能不能给对方讲清楚
1. 坐标系
在Android坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴。如下所示:
除了Android坐标系,还存在View坐标系,View坐标系内部关系如图所示。
2. 自定义属性
Android系统的控件以android开头的都是系统自带的属性。为了方便配置自定义View的属性,我们也可以自定义属性值。
Android自定义属性可分为以下几步:
- 自定义一个View
- 编写values/attrs.xml,在其中编写styleable和item等标签元素
- 在布局文件中View使用自定义的属性(注意namespace)
- 在View的构造方法中通过TypedArray获取
自定义View属性很重要,但是并不复杂,需要的话再查一下就好了
3. View绘制流程
View的绘制基本由measure()、layout()、draw()这个三个函数完成
函数 | 作用 | 相关方法 |
---|---|---|
measure() | 测量View的宽高 | measure(),setMeasuredDimension(),onMeasure() |
layout() | 计算当前View以及子View的位置 | layout(),onLayout(),setFrame() |
draw() | 视图的绘制工作 | draw(),onDraw() |
3.1 MeasureSpec
MeasureSpec
是View的内部类,它封装了一个View的尺寸,在onMeasure()
当中会根据这个MeasureSpec
的值来确定View的宽高。
MeasureSpec
的值保存在一个int值当中。一个int值有32位,前两位表示模式mode
后30位表示大小size
。即MeasureSpec
= mode
+ size
。
在MeasureSpec
当中一共存在三种mode
:UNSPECIFIED
、EXACTLY
和AT_MOST
。
对于View来说,MeasureSpec
的mode和Size有如下意义
模式 | 意义 | 对应 |
---|---|---|
EXACTLY | 精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size | match_parent |
AT_MOST | 最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值 | wrap_content |
UNSPECIFIED | 无限制,View对尺寸没有任何限制,View设置为多大就应当为多大 | 一般系统内部使用 |
3.2 Layout()
layout()
过程,对于View
来说用来计算View
的位置参数,对于ViewGroup
来说,除了要测量自身位置,还需要测量子View
的位置。
3.3 Draw()
draw流程也就是的View绘制到屏幕上的过程,整个流程的入口在View
的draw()
方法之中,而源码注释也写的很明白,整个过程可以分为6个步骤。
- 如果需要,绘制背景。
- 有过有必要,保存当前canvas。
- 绘制View的内容。
- 绘制子View。
- 如果有必要,绘制边缘、阴影等效果。
- 绘制装饰,如滚动条等等。
使用下方的流程图表示:
布局过程的自定义:
方式: 重写布局过程的相关方法\
1. 测量过程: onMeasure()
2. 布局过程: onLayout()
复制代码
具体:
1. 重写onMeasure()来修改已有的View的尺寸
2. 重写onMeasure()来全新计算自定义View的尺寸
3. 重写onMeasure()和onLayout()来全新计算自定义 ViewGroup 的内部布局
复制代码
public class SquareImageView extends AppCompatImageView {
private static final String TAG = "SquareImageView";
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 先执行原测量算法
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取原先的测量结果
int measureWidth = getMeasuredWidth();
int measureHeight = getMeasuredHeight();
Log.d(TAG, "onMeasure11" +
", measureWidth = " + measureWidth +
", measureHeight = " + measureHeight +
"");
// 利用原先的测量结果计算出新尺寸
if (measureWidth > measureHeight) {
measureWidth = measureHeight;
} else {
measureHeight = measureWidth;
}
Log.d(TAG, "onMeasure22" +
", measureWidth = " + measureWidth +
", measureHeight = " + measureHeight +
"");
// 保存计算后的结果
setMeasuredDimension(measureWidth, measureHeight);
}
}
复制代码
重写onMeasure() 修改尺寸
1. 重写 onMeasure() 修改尺寸,并调用super.onMeasure触发原先的测量
2. 用getMeasuredWidth() 和 getMeasuredHeight() 取到之前测得的尺寸,利用这两个尺寸来计算出最终尺寸。
3. 使用 setMeasuredDimension() 保存尺寸
复制代码