移动开发中的自定义视图与绘制:本质、哪些API及工作原理

作者: IT Sectr 发布日期: 2026-08-01 阅读时间: 11 分钟

自定义视图是任何移动应用独特界面的基础。移动应用中的自定义视图允许您创建标准组件中不可用的自有图形、动画和元素。根据Apple Developer,2025drawRect仍然是iOS移动开发中自定义绘制的主要方法。在Android上,onDraw与Canvas扮演着相同角色,而在Flutter中——CustomPainter。

要点

  • 自定义视图 — 移动应用中具有自有绘制、大小和行为逻辑的用户定义组件。
  • drawRect — iOS上通过Core Graphics和CGContext进行自定义绘制的UIView方法。
  • onDraw — Android上通过Canvas和Paint并结合onMeasure测量系统进行绘制的View方法。
  • CustomPainter — Flutter中用于在Canvas上绘制并具有paint和shouldRepaint方法的类。
  • 测量 — 通过iOS上的sizeThatFits、Android上的onMeasure和Flutter中的computeDryLayout确定视图尺寸的过程。

什么是自定义视图以及为什么移动应用中需要它们

自定义视图是用户定义的界面组件,它覆盖标准的绘制和测量方法以创建独特的图形。图表、动画、示意图、自定义聊天元素和AR面具 — 这些是移动应用中自定义视图的典型使用场景。标准UI组件(Button、TextView、Label)覆盖了80%的任务,但剩余20%需要在移动开发中完全控制绘制。移动开发中的绘制是一项区分初级开发者和高级开发者的技能。

何时自定义视图是合理的

移动应用中的自定义视图对于独特的2D图形、复杂动画、图表和数据可视化是必需的。如果标准UIKit不提供所需元素 — 则创建自定义视图。在移动应用中,自定义视图用于进度条、自定义按钮、渐变填充、动画图标和手指绘制画布。重要的是不要过度使用 — 对于简单样式,通过外观API配置标准组件就足够了。

iOS上的自定义视图:drawRect和Core Graphics

在iOS上,自定义视图通过继承UIView并覆盖drawRect方法来创建。drawRect在首次渲染时以及每次setNeedsDisplay()调用后被调用。在drawRect内部,开发者通过UIGraphicsGetCurrentContext()获取CGContext — 用于线条、形状、渐变和文本的画布。Core Graphics(Quartz 2D)是一个低级别的基于CPU的渲染引擎,提供对每个像素的完全控制。UIBezierPath是一个方便的包装器,用于以简洁的语法创建路径。

swift
class CircleView: UIView {
    private var fillColor: UIColor = .systemBlue

    override func drawRect(_ rect: CGRect) {
        guard let ctx = UIGraphicsGetCurrentContext() else { return }
        ctx.setFillColor(fillColor.cgColor)
        ctx.addEllipse(in: bounds.insetBy(dx: 4, dy: 4))
        ctx.drawPath(using: .fill)
    }

    func updateColor(_ newColor: UIColor) {
        fillColor = newColor
        setNeedsDisplay()
    }
}

Core Graphics:CGContext、CGGradient和变换

Core Graphics提供CGContext — 用于所有绘制操作的画布。CGGradient创建线性和径向渐变,支持CGColorSpace(sRGB、Display P3)。变换通过CGAffineTransform执行 — 用于旋转、缩放和平移的3x3矩阵。CGContextTranslateCTM、CGContextRotateCTM和CGContextScaleCTM修改坐标系。Core Graphics在CPU上运行,因此复杂的图形可能会加载处理器 — 使用Metal进行GPU加速。

用于创建形状的UIBezierPath

UIBezierPath是CGPath上的面向对象包装器。addArc(withCenter:radius:startAngle:endAngle:clockwise:)方法创建圆弧和圆,addCurve创建贝塞尔曲线。路径通过addClip支持联合、交集和减集操作。UIBezierPath适用于自定义遮罩(maskView)、裁剪路径和复杂形状 — 星形、心形、自定义图标。

Android上的自定义视图:onDraw和Canvas

在Android上,自定义视图通过覆盖onDraw(Canvas)来扩展View。Canvas包含移动开发中的所有绘制方法:drawCircle、drawRect、drawLine、drawText、drawBitmap、drawPath和drawRoundRect。Paint配置参数 — 颜色、线条粗细、样式(FILL、STROKE、FILL_AND_STROKE)、抗锯齿(isAntiAlias)和文本大小。onDraw在每次invalidate()时被调用,不应包含重型操作 — 所有计算都被缓存。

kotlin
class CustomCircleView(context: Context, attrs: AttributeSet?)
    : View(context, attrs) {

    private val paint = Paint().apply {
        style = Paint.Style.FILL
        color = Color.BLUE
        isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val cx = width / 2f
        val cy = height / 2f
        val radius = minOf(cx, cy) - 4f
        canvas.drawCircle(cx, cy, radius, paint)
    }
}

Android上的路径和动画

Android上的Path等同于UIBezierPath:addCircle、addRect、addOval、lineTo、cubicTo和quadTo。PathMeasure计算路径长度和任意点的坐标 — 用于沿着路径动画化对象移动。对于动画,使用ValueAnimator并在回调中调用invalidate()。动画器可以更改颜色、半径、位置 — 任何影响绘制的属性。Android上的自定义视图还支持StateListDrawable和animated-vector以通过XML进行动画。

Canvas中的着色器和渐变

Android上的渐变通过Shader设置 — LinearGradient、RadialGradient和SweepGradient。Shader通过paint.setShader(shader)分配给Paint。LinearGradient在两点之间生成线性渐变,带有可选的颜色位置。RadialGradient — 从中心到边缘的径向。SweepGradient — 围绕中心点的圆锥形。Shader.TileMode(CLAMP、REPEAT、MIRROR)定义超出区域边界的行为。

Flutter中的CustomPainter:在Canvas上绘制

Flutter使用CustomPainter — 一个具有paint(Canvas canvas, Size size)和shouldRepaint方法的类。Flutter中的Canvas提供与Android相同的方法:drawCircle、drawRect、drawLine、drawPath、drawParagraph和drawImage。Flutter中的Paint配置颜色、粗细、样式和抗锯齿。Flutter不使用drawRect或onDraw — 所有绘制通过嵌入在CustomPaint小部件中的CustomPainter进行。

dart
class CirclePainter extends CustomPainter {
  final Color color;

  CirclePainter({required this.color});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill
      ..isAntiAlias = true;
    final radius = size.shortestSide / 2 - 4;
    canvas.drawCircle(size.center(Offset.zero), radius, paint);
  }

  @override
  bool shouldRepaint(CirclePainter oldDelegate) =>
    oldDelegate.color != color;
}

shouldRepaint和RepaintBoundary

shouldRepaint确定是否需要重新绘制CustomPainter。仅在影响渲染的数据更改时返回true — 颜色、大小、图表数据。RepaintBoundary将重绘隔离到单独的图层中 — 用于很少更改的小部件。Flutter没有setNeedsDisplay或invalidate — 重绘通过setState或监听更改的ValueNotifier触发。

Flutter中的渐变和变换

Flutter支持LinearGradient、RadialGradient和SweepGradient — 与Android中相同的类型。Gradient通过createShader(Rect bounds)为Paint创建Shader。变换通过canvas.rotate、canvas.scale、canvas.translate和canvas.skew执行。Flutter为Transform小部件使用4x4变换矩阵(Matrix4)。Flutter中的绘制动画使用AnimationController与setState结合来重新绘制CustomPainter。

自定义视图的测量:sizeThatFits、onMeasure和computeDryLayout方法

自定义视图的正确测量是可预测界面行为的基础。在iOS上,视图的大小通过sizeThatFits和intrinsicContentSize确定。sizeThatFits返回给定约束下的最佳大小。intrinsicContentSize — 没有外部约束时内容的自然大小。在Android上,测量在onMeasure中通过MeasureSpec参数(UNSPECIFIED、EXACTLY、AT_MOST)实现。在Flutter中,大小传递给paint(Canvas, Size)并可以通过computeDryLayout或LayoutBuilder计算。

iOS上的sizeThatFits和intrinsicContentSize

intrinsicContentSize返回CGSize — 基于其内容的视图的自然大小。setContentHuggingPriority和setContentCompressionResistancePriority控制拉伸和压缩时的行为。sizeThatFits由系统调用以在Auto Layout中计算大小。对于自定义视图,如果您的组件具有自然大小,请覆盖intrinsicContentSize — 例如,圆形应始终为100x100。

Android上的onMeasure和MeasureSpec

onMeasure接受widthMeasureSpec和heightMeasureSpec。MeasureSpec包含模式和大小:UNSPECIFIED(无约束,视图自行选择大小)、EXACTLY(来自父级的精确大小)、AT_MOST(最大大小)。setMeasuredDimension(width, height)保存测量结果。当数据更改时,调用requestLayout()重新测量。嵌套的ViewGroup需要覆盖onLayout以排列子元素。

平台测量方法更改时调用返回
iOSsizeThatFits / intrinsicContentSizesetNeedsLayout / invalidateIntrinsicContentSizeCGSize
AndroidonMeasure / setMeasuredDimensionrequestLayout / invalidatevoid(保存大小)
FluttercomputeDryLayoutsetState / LayoutBuilderSize

Flutter中的computeDryLayout

computeDryLayout是一种RenderObject方法,用于在没有完整布局周期的情况下计算大小。LayoutBuilder.getSize — 在build方法中获取可用空间的替代方法。在Flutter中,画布大小作为Size传递给paint,不需要单独测量 — CustomPainter在传递的边界内绘制。但是,对于自定义RenderObject,您需要覆盖performLayout和computeDryLayout。

常见问题

iOS上的drawRect是什么以及何时调用?

drawRect是通过Core Graphics进行自定义绘制的UIView方法。它在视图首次渲染时以及每次setNeedsDisplay()调用后被调用。在drawRect内使用CGContext绘制线条、形状和文本。

如何使用Canvas在Android上绘制?

创建一个扩展View的类,覆盖onDraw(Canvas canvas)并使用Canvas API:drawCircle、drawRect、drawPath。Paint配置颜色、粗细和样式。要更新,请调用invalidate()。

Flutter中的CustomPainter是什么?

CustomPainter是Flutter中用于在Canvas上进行自定义绘制的类。实现paint(Canvas, Size)和shouldRepaint方法。它通过CustomPaint小部件放置。Flutter没有drawRect — 所有绘制通过CustomPainter进行。

如何测量自定义视图的大小?

在iOS上使用sizeThatFits和intrinsicContentSize。在Android上 — 使用MeasureSpec的onMeasure。在Flutter中,大小传递给paint(Canvas, Size)或通过computeDryLayout计算。每个平台使用自己的测量方法。

何时使用自定义视图而不是标准组件?

自定义视图对于无法使用标准UIKit或View实现的独特图形、动画、图表和元素是必需的。对于简单样式,配置标准组件就足够了 — 不要不必要地复杂化架构。

总结

  • 自定义视图 — 移动应用中图表、动画和非标准元素的独特界面的基础。
  • drawRect — 通过Core Graphics使用CGContext和UIBezierPath进行绘制的iOS方法。
  • onDraw — 通过Canvas和Paint使用Path和Shader支持进行绘制的Android方法。
  • CustomPainter — 使用shouldRepaint和RepaintBoundary在Canvas上绘制的Flutter类。
  • 测量 — 通过sizeThatFits(iOS)、onMeasure(Android)和computeDryLayout(Flutter)进行大小验证。
  • 移动开发中的绘制在所有平台上遵循统一模式:获取上下文、配置样式和绘制基元。
  • 缓存绘制对象(Paint、Path) — 不要在drawRect、onDraw或paint内部创建它们以获得稳定的性能。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目