「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」
1. 自定义View的分类
2. 自定义View的构造函数
| 构造函数 | 调用场景 |
|---|---|
| public View(Context context) | View是在Java代码里面new的 |
| public View(Context context, @Nullable AttributeSet attrs) | View是在.xml里声明的 |
| public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) | View有style属性时 |
| public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) | View有style属性时、API21之后才使用 |
3. ViewGroup.LayoutParams
指定视图View的宽高等布局参数
| 参数 | 说明 |
|---|---|
| fill_parent | 强制性使子视图的大小扩展至与父视图大小相等(不含 padding ) |
| match_parent | 与fill_parent相同,用于Android 2.3 & 之后版本 |
| wrap_content | 自适应大小,强制性地使视图扩展以便显示其全部内容(含 padding ) |
| XX dip | 精确设置高度值 |
4. MeasureSpec
- 是测量View大小的依据,每一个MeasureSpec代表一组宽度(widthMeasureSpec)/高度(heightMeasureSpec)的测量规格
- 宽测量规格 + 高测量规格 决定了视图View的大小
- 根据View的类型分为两种情况:
5. View绘制流程
View的绘制基本由onMeasure()、onLayout()、onDraw()三个函数组成
6. onMeasure()
| View类型 | measure过程 |
|---|---|
| 单一View | 只测量自身 |
| ViewGroup | 对ViewGroup视图中所有的子View都进行测量 |
单一View的measure过程
Measure过程的入口;属于View.java类 & final类型,即子类不能重写此方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
...
}
- 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
- 存储测量后的View宽/高:setMeasuredDimension()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
- 存储测量后的View宽/高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
- 根据View宽/高的测量规格计算View的宽/高值
- UNSPECIFIED: 使用提供的默认大小
- AT_MOST/EXACTLY: 使用View测量后的宽/高值 = measureSpec中的Size
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
ViewGroup的measure过程
- 从ViewGroup至子View、自上而下遍历进行(即树形递归),通过计算整个ViewGroup中各个View的属性,从而最终确定整个ViewGroup的属性
- onMeasure()方法的作用是测量View的宽/高值,而不同的ViewGroup(如LinearLayout、RelativeLayout、自定义ViewGroup子类等)具备不同的布局特性,这导致它们的子View测量方法各有不同,所以onMeasure()的实现也会有所不同。因此,ViewGroup无法对onMeasure()作统一实现。这个也是单一View的measure过程与ViewGroup的measure过程最大的不同
- 针对Measure流程,自定义ViewGroup的关键在于:根据需求复写onMeasure(),从而实现子View的测量逻辑。复写onMeasure()的步骤主要分为三步: (1)遍历所有子View及测量:measureChildren();(2)合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值:需自定义实现;(3)存储测量后View宽/高的值:setMeasuredDimension()
7. View位置
View的位置由4个顶点决定
- Left:子View左边界到父view左边界的距离
- Top: 子View上边界到父view上边界的距离
- Right:子View右边界到父view左边界的距离
- Bottom:子View下边距到父View上边界的距离
8. onLayout()
单一View的onLayout过程
- 由于单一View是没有子View的,故onLayout()是一个空实现
ViewGroup的onLayout过程
从ViewGroup至子View、自上而下遍历进行(即树形递归),通过计算整个ViewGroup中各个View的属性,从而最终确定整个ViewGroup的属性
- 定义为抽象方法,需重写,因为子View的确定位置与具体布局有关,所以onLayout()在ViewGroup没有实现
- 复写原理:遍历子View 、计算当前子View的四个位置值 & 确定自身子View的位置
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
9. onDraw()
单一View的onDraw()过程
根据给定的 Canvas 自动渲染View包括其所有子 View)
- View绘制自身(含背景、内容)
- 绘制装饰(滚动指示器、滚动条、和前景)
ViewGroup的onDraw()过程
- ViewGroup绘制自身(含背景、内容)
- ViewGroup遍历子View & 绘制其所有子View
- l ViewGroup绘制装饰(滚动指示器、滚动条、和前景)