自定义控件技巧
自定义控件技巧
自定义控件技巧
自定义 ViewGroup
自定义 ViewGroup 需要实现相关的 LayoutParams 方法
onMeasure 多次调用
onMeasure 可能会调用多次,具体看父布局;如果存在 onMeasure 变量赋值的,需要在 onMeasure 清空
setWillNotDraw 设置的条件
默认:
ViewGroup
默认调用setWillNotDraw(true)
以跳过绘制优化(因其通常作为容器,不自身绘制内容)。需手动禁用此优化。- 继承自
View
,默认未设置WILL_NOT_DRAW
标记,可直接重写onDraw()
,无需调用setWillNotDraw(false)
。
设置了 background,默认会添加上 ~PFLAG_SKIP_DRAW
标记,需要绘制
1
2
3
4
5
6
7
8
9
10
// View.java
public void setBackgroundDrawable(Drawable background) {
if (background != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
}
// ...
}
setWillNotDraw 行为:
1
2
3
4
5
6
7
8
是否需要自定义绘制内容?
├── 是 → setWillNotDraw(false)
├── 否 →
├── 是否设置了背景/前景?
│ ├── 是 → 无需操作(系统自动处理)
│ └── 否 → 保持默认 setWillNotDraw(true)
└── 是否继承自修改过 willNotDraw 的父类?
└── 检查父类逻辑,必要时显式配置
getChildDrawingOrder 自定义绘制顺序
作用: 在 Android 自定义 ViewGroup
时,getChildDrawingOrder()
方法用于控制子 View 的绘制顺序。默认情况下,子 View 按照添加顺序绘制(后添加的 View 覆盖在先添加的 View 上),但通过重写此方法,可以实现自定义的绘制顺序(如反向绘制、按特定条件排序等)。
前提: 设置了 setChildrenDrawingOrderEnabled(boolean enable)
核心代码:
1
2
3
4
5
6
7
8
9
10
11
public class CustomLayout extends ViewGroup {
public CustomLayout(Context context) {
super(context);
setChildrenDrawingOrderEnabled(true); // 必须启用
}
@Override
protected int getChildDrawingOrder(int childCount, int drawingPosition) {
// 默认实现:return drawingPosition;
// 自定义逻辑:返回实际需要绘制的子 View 索引
}
}
示例:
- 默认正序,按添加的顺序
- 倒序绘制
1
2
3
4
5
@Override
protected int getChildDrawingOrder(int childCount, int drawingPosition) {
// 反向绘制:最后一个子 View 先绘制,第一个子 View 最后绘制
return childCount - 1 - drawingPosition;
}
- 动态置顶某个子 View
1
2
3
4
5
6
7
8
9
10
11
12
private int mTopChildIndex = 0; // 需要置顶的子 View 索引
@Override
protected int getChildDrawingOrder(int childCount, int drawingPosition) {
if (drawingPosition == childCount - 1) {
return mTopChildIndex; // 最后一个绘制的子 View(显示在最上层)
} else if (drawingPosition >= mTopChildIndex) {
return drawingPosition + 1; // 跳过已处理的置顶子 View
} else {
return drawingPosition;
}
}
- 按 Z 轴值排序
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected int getChildDrawingOrder(int childCount, int drawingPosition) {
// 假设子 View 实现了 Z 轴接口,按 Z 轴值从小到大排序
List<View> sortedChildren = new ArrayList<>();
for (int i = 0; i < childCount; i++) {
sortedChildren.add(getChildAt(i));
}
Collections.sort(sortedChildren, (v1, v2) ->
Float.compare(v1.getZ(), v2.getZ()));
// 返回排序后的索引
return indexOfChild(sortedChildren.get(drawingPosition));
}
疑惑:
子 View 的触摸事件顺序是否受影响? 不影响。触摸事件的分发顺序由
ViewGroup
的onInterceptTouchEvent
和子 View 的添加顺序决定,与绘制顺序无关。如何实现点击子 View 后置顶?
- 在点击事件中更新
mTopChildIndex
。 - 调用
invalidate()
触发重绘。 - 在
getChildDrawingOrder()
中动态调整顺序。
- 在点击事件中更新
layout RTL 适配封装
状态保存
- onSaveInstanceState
- onRestoreInstanceState
其他
内存抖动
onMeasure, onLayout, onDraw 调用非常频繁,不要在这些方法中 new 对象,否则容易出现内存抖动
高效自定义 View
本文由作者按照 CC BY 4.0 进行授权