View的绘制原理
1. 基础知识
1.1 View绘制流程的三个阶段
-
测量(Measure)
- 确定View的大小
- 自上而下遍历View树
- 父View向子View传递测量要求
-
布局(Layout)
- 确定View的位置
- 计算View的left、top、right、bottom
- 根据测量结果和布局参数进行定位
-
绘制(Draw)
- 将View绘制到屏幕上
- 绘制背景、内容、子View、前景等
- 处理透明度、动画等效果
2. 核心问题
2.1 View的测量过程是怎样的?
答案:
-
MeasureSpec的生成
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 精确模式,指定具体大小 public static final int EXACTLY = 1 << MODE_SHIFT; // 最大模式,View的大小不能超过指定大小 public static final int AT_MOST = 2 << MODE_SHIFT; // 未指定模式,View按需求取值 public static final int UNSPECIFIED = 0; } -
measure过程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec) ); }
2.2 自定义View需要注意什么?
-
正确重写测量方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取测量模式和大小 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 计算实际大小 int width = calculateWidth(widthMode, widthSize); int height = calculateHeight(heightMode, heightSize); // 保存测量结果 setMeasuredDimension(width, height); } -
处理wrap_content
- 指定默认大小
- 避免与match_parent效果相同
-
考虑padding和margin
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 考虑padding int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int width = getWidth() - getPaddingRight() - paddingLeft; int height = getHeight() - getPaddingBottom() - paddingTop; }
2.3 View的绘制流程是从哪里开始的?
答案:
-
ViewRootImpl的performTraversals方法
- 开始View树的遍历
- 触发measure、layout、draw三大流程
-
绘制流程的传递
private void performTraversals() { // 执行测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 执行布局 performLayout(lp, mWidth, mHeight); // 执行绘制 performDraw(); }
3. 性能优化
3.1 如何优化View的绘制性能?
-
避免过度绘制
- 移除不必要的背景
- 使用clipRect裁剪画布
- 使用ViewStub延迟加载
-
扁平化View层级
<!-- 优化前 --> <LinearLayout> <LinearLayout> <LinearLayout> <TextView/> </LinearLayout> </LinearLayout> </LinearLayout> <!-- 优化后 --> <ConstraintLayout> <TextView/> </ConstraintLayout> -
使用硬件加速
<application android:hardwareAccelerated="true">
4. 实战示例
4.1 自定义圆形进度条
public class CircleProgressView extends View {
private Paint mPaint;
private int mProgress;
private int mMax = 100;
public CircleProgressView(Context context) {
this(context, null);
}
public CircleProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = Math.min(width, height);
setMeasuredDimension(size, size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(centerX, centerY) - 10;
// 绘制背景圆
mPaint.setColor(Color.GRAY);
canvas.drawCircle(centerX, centerY, radius, mPaint);
// 绘制进度
mPaint.setColor(Color.BLUE);
float sweepAngle = 360f * mProgress / mMax;
RectF rectF = new RectF(centerX - radius, centerY - radius,
centerX + radius, centerY + radius);
canvas.drawArc(rectF, -90, sweepAngle, false, mPaint);
}
public void setProgress(int progress) {
mProgress = progress;
invalidate();
}
}4.2 使用示例
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CircleProgressView progressView = findViewById(R.id.progressView);
// 设置进度
progressView.setProgress(75);
}
}<com.example.CircleProgressView
android:id="@+id/progressView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"/>