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

Flutter自定义circle_wave环形波纹组件无法调整宽高问题求助

Flutter环形波纹组件尺寸调整方案

问题原因

  • 原代码中CustomPaint外层使用Positioned.fill布局,会强制占满父容器的全部可用空间,自定义的宽高约束无法生效
  • 绘制逻辑中波纹的最大半径硬编码为固定值165,不会跟随组件的实际尺寸变化

完整修改代码

import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';

// 原common.dart中的CounterText实现,可根据自己的需求调整样式
class CounterText extends StatelessWidget {
  final int counter;
  const CounterText({super.key, required this.counter});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        '$counter',
        style: const TextStyle(color: Colors.white, fontSize: 80, fontWeight: FontWeight.bold),
      ),
    );
  }
}

// 原common.dart中的map映射函数
double map(double value, double minIn, double maxIn, double minOut, double maxOut) {
  return (value - minIn) * (maxOut - minOut) / (maxIn - minIn) + minOut;
}

class DemoCircleWave extends StatefulWidget {
  const DemoCircleWave({
    super.key,
    this.width = 350,
    this.height = 350,
  });
  // 新增可自定义的宽高参数
  final double width;
  final double height;

  @override
  _DemoCircleWaveState createState() => _DemoCircleWaveState();
}

class _DemoCircleWaveState extends State<DemoCircleWave>
    with TickerProviderStateMixin {
  static const colors = <Color>[
    Color(0xFFFF2964),
    Color(0xFF32FF3A),
    Color(0xFF4255FF)
  ];
  late AnimationController controller;
  late AnimationController addPointController;
  late Animation<double> addPointAnimation;
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      upperBound: 2,
      duration: const Duration(seconds: 10),
    )..repeat();
    addPointController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    addPointAnimation =
        addPointController.drive(CurveTween(curve: Curves.ease));
  }

  @override
  void dispose() {
    controller.dispose();
    addPointController.dispose();
    super.dispose();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
      addPointController.forward(from: 0);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Circle wave')),
      backgroundColor: Colors.black,
      body: Center(
        // 用SizedBox指定波纹组件的尺寸
        child: SizedBox(
          width: widget.width,
          height: widget.height,
          child: Stack(
            alignment: Alignment.center,
            children: [
              for (int i = 0; i < 3; i++)
                TweenAnimationBuilder(
                  tween: Tween<double>(begin: 0, end: 1),
                  duration: const Duration(milliseconds: 500),
                  curve: Curves.easeIn,
                  builder: (_, double opacity, __) {
                    return CustomPaint(
                      size: Size(widget.width, widget.height),
                      painter: CircleWavePainter(
                        controller,
                        addPointAnimation,
                        i,
                        colors[i].withOpacity(opacity),
                        _counter,
                      ),
                    );
                  },
                ),
              CounterText(counter: _counter),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class CircleWavePainter extends CustomPainter {
  CircleWavePainter(
    this.animation,
    this.addAnimation,
    this.index,
    this.color,
    this.count,
  ) : super(repaint: animation);
  final Animation<double> animation;
  final Animation<double> addAnimation;
  final int index;
  final Color color;
  final int count;

  static const halfPi = math.pi / 2;
  static const twoPi = math.pi * 2;
  final n = 7;

  @override
  void paint(Canvas canvas, Size size) {
    final t = animation.value;
    final halfWidth = size.width / 2;
    final halfHeight = size.height / 2;
    final q = index * halfPi;
    // 动态计算最大半径,减去描边宽度避免波纹超出边界
    final maxRadius = math.min(halfWidth, halfHeight) - 4;

    List<Offset> computeOffsets(int length) {
      final offsets = <Offset>[];
      for (var i = 0; i < length; i++) {
        final th = i * twoPi / length;
        double os = map(math.cos(th - twoPi * t), -1, 1, 0, 1);
        os = 0.125 * math.pow(os, 2.75);
        // 替换硬编码的165为动态计算的半径
        final r = maxRadius * (1 + os * math.cos(n * th + 1.5 * twoPi * t + q));
        offsets.add(Offset(
            r * math.sin(th) + halfWidth, -r * math.cos(th) + halfHeight));
      }
      return offsets;
    }

    final offsets = computeOffsets(count);

    if (count > 1 && addAnimation.value < 1) {
      final t = addAnimation.value;
      final oldOffsets = computeOffsets(count - 1);
      for (var i = 0; i < count - 1; i++) {
        offsets[i] = Offset.lerp(oldOffsets[i], offsets[i], t)!;
      }
      offsets[count - 1] = Offset.lerp(
        oldOffsets[count - 2],
        offsets[count - 1],
        t,
      )!;
    }

    final path = Path()..addPolygon(offsets, true);
    canvas.drawPath(
      path,
      Paint()
        ..blendMode = BlendMode.lighten
        ..color = color
        ..strokeWidth = 8
        ..style = PaintingStyle.stroke,
    );
  }

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

使用方法

需要调整组件尺寸时,直接在实例化DemoCircleWave时传入宽高参数即可,示例:

DemoCircleWave(width: 200, height: 200)

内容的提问来源于stack exchange,提问作者DolDurma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 05:15:00