You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter CustomPainter绘制时钟指针不显示问题求助

解决CustomPainter绘制时钟指针不显示的问题

核心问题分析

你的代码存在几个关键错误,直接导致指针(包括drawCircle)无法显示:

  • paint方法不能异步:CustomPainter的paint是同步执行的方法,添加async会破坏绘制流程,导致后续绘制逻辑无法正常执行。
  • 旋转中心错误:默认canvas.rotate是绕坐标系原点(0,0)旋转,而非钟的中心,指针会被转到可视区域外。
  • 图片源Rect错误:秒针的源Rect用了钟的中心坐标作为起始点,会从图片中间(甚至超出范围)截取内容,自然绘制不出。
  • 指针位置偏移:目标Rect起始点设为钟的中心,未考虑指针自身尺寸,导致位置完全偏离预期区域。

修正后的完整代码

import 'dart:async';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

class CustomClockPage extends StatefulWidget {
  final ui.Image clockImage;
  final ui.Image secondImage;

  const CustomClockPage({
    required this.clockImage,
    required this.secondImage,
    Key? key,
  }) : super(key: key);

  @override
  State<CustomClockPage> createState() => _CustomClockPageState();
}

class _CustomClockPageState extends State<CustomClockPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Positioned(
            left: 50,
            top: 100,
            child: CustomClock(
              clockImage: widget.clockImage,
              secondImage: widget.secondImage,
            ),
          ),
        ],
      ),
    );
  }
}

class CustomClock extends StatefulWidget {
  final ui.Image clockImage;
  final ui.Image secondImage;

  const CustomClock({
    required this.clockImage,
    required this.secondImage,
    Key? key,
  }) : super(key: key);

  @override
  State<CustomClock> createState() => _CustomClockState();
}

class _CustomClockState extends State<CustomClock> {
  late DateTime now;

  @override
  void initState() {
    super.initState();
    now = DateTime.now();
    Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        now = DateTime.now();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final clockWidth = MediaQuery.of(context).size.width / 2;
    final clockHeight = (clockWidth / widget.clockImage.width) * widget.clockImage.height;
    return SizedBox(
      width: clockWidth,
      height: clockHeight,
      child: CustomPaint(
        painter: WatchPainter(
          now: now,
          clockImage: widget.clockImage,
          secondImage: widget.secondImage,
        ),
      ),
    );
  }
}

class WatchPainter extends CustomPainter {
  final DateTime now;
  final ui.Image clockImage;
  final ui.Image secondImage;

  WatchPainter({
    required this.now,
    required this.clockImage,
    required this.secondImage,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final xCenter = size.width / 2;
    final yCenter = size.height / 2;
    final scaleRatio = size.width / clockImage.width;

    renderClock(canvas, size);
    renderHands(canvas, size, xCenter, yCenter, scaleRatio);
  }

  void renderClock(Canvas canvas, Size size) {
    final src = Rect.fromLTWH(0, 0, clockImage.width.toDouble(), clockImage.height.toDouble());
    final dst = Rect.fromLTWH(0, 0, size.width, size.height);
    canvas.drawImageRect(clockImage, src, dst, Paint());
  }

  void renderHands(Canvas canvas, Size size, double xCenter, double yCenter, double scaleRatio) {
    final secondRotation = now.second * (2 * math.pi / 60);

    canvas.save();
    // 将画布原点平移到钟的中心,旋转将围绕此点进行
    canvas.translate(xCenter, yCenter);
    canvas.rotate(secondRotation);

    // 秒针源Rect取完整图片
    final secondSrc = Rect.fromLTWH(0, 0, secondImage.width.toDouble(), secondImage.height.toDouble());
    final scaledSecondWidth = secondImage.width * scaleRatio;
    final scaledSecondHeight = secondImage.height * scaleRatio;
    // 让秒针中心对齐到钟的中心(可根据图片实际锚点调整偏移)
    final secondDst = Rect.fromLTWH(
      -scaledSecondWidth / 2,
      -scaledSecondHeight / 2,
      scaledSecondWidth,
      scaledSecondHeight,
    );
    canvas.drawImageRect(secondImage, secondSrc, secondDst, Paint());

    // 测试用红色圆点,验证中心位置
    canvas.drawCircle(Offset.zero, 5, Paint()..color = Colors.red);

    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

关键修改说明

  1. 移除async修饰符:paint方法必须同步,异步会导致绘制逻辑失效。
  2. 调整旋转逻辑:通过canvas.translate(xCenter, yCenter)将原点移到钟的中心,后续旋转和绘制都围绕此点进行。
  3. 修正图片源Rect:secondSrc改为从(0,0)开始,截取完整的秒针图片。
  4. 对齐指针位置:目标Rect起始坐标设为负的一半尺寸,让指针中心对齐到钟的中心(可根据秒针图片的实际锚点调整偏移,比如让根部对齐中心)。
  5. 简化缩放比例计算:直接用绘制区域宽度除以钟面图片宽度,无需额外传入参数。

测试建议

如果仍看不到指针,可先注释钟面绘制代码,单独测试指针和红色圆点是否显示,确认位置正确后再叠加钟面。

内容的提问来源于stack exchange,提问作者hyeongrae-kim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 21:25:07