自定义 View 的三种方式

系统控件不够用时才自定义 View。三种做法:继承 View 重写 onDraw 画内容;继承已有控件(如 TextView)在它的基础上叠加绘制;组合多个控件包成一个自定义组件。这篇讲第一种,也是最自由的一种。

自定义 View 的舞台是 onDraw,画笔是 Paint,画布是 Canvas。

新建类继承 View,重写 onDraw,用 Canvas 画圆、画线、画文字,再在布局里用包名引用。绘制是每帧全量重画的,性能瓶颈往往在 onDraw 里干了太多活,所以绘制代码要轻、要能复用。

onMeasure 与测量

View 被放进去之前先要量尺寸。onMeasure 收到父布局的约束(宽高模式),自己决定最终尺寸。不重写的话用默认的测量,wrap_content 会变成撑满,这是新手最常见的坑。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int w = MeasureSpec.getSize(widthMeasureSpec);
  int h = MeasureSpec.getSize(heightMeasureSpec);
  if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
    w = Math.min(w, dp(160)); // wrap_content 时给个合理值
  }
  setMeasuredDimension(w, h);
}
测量速查
  • EXACTLY父布局定死了。
  • AT_MOST上限约束,wrap_content。
  • UNSPECIFIED没有约束,少见。

Canvas 基础绘制

onDraw 里 Canvas 提供全套绘制命令:画圆、画弧、画线、画路径、画文字。坐标系原点在左上角,单位是像素,注意适配屏幕密度。

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  paint.setColor(0xFF3B82F6);
  canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, dp(60), paint);
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(dp(6));
  canvas.drawArc(box, 0, 270, false, paint);
}
Android 自定义 View:一支发光画笔在画布上画出圆形、三角形、圆角矩形几何图形组、周围坐标网格线的蓝调插画
画笔在画布上勾勒几何图形。

绘制顺序决定遮挡关系,后画的盖住先画的。把 Paint 对象建一次放在字段里,别在 onDraw 里 new,否则每帧都在创建对象,卡顿随之而来。

Paint 高级用法

Paint 不只是颜色和粗细:线性渐变 Shader、阴影 setShadowLayer、圆角 Path、抗锯齿都能让绘制细腻起来。进度条、仪表盘这类控件全靠这些组合。

paint.setShader(new LinearGradient(0, 0, getWidth(), 0,
  new int[]{0xFF3B82F6, 0xFF06B6D4}, null, Shader.TileMode.CLAMP));
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setShadowLayer(dp(8), 0, 0, 0x553B82F6);

渐变、阴影用多了会掉帧,尤其阴影在低端机上很贵。能用实色表达就别开阴影,开了也控制范围。复杂图形拆成 Path 一次绘制,比几十条 drawLine 高效得多。