Flutter小技巧之魔改widget

算是分享一个Flutter中的小技巧,当然这也不只适用flutter的,其他的像Android中也可以,就是为了快速实现xx设计的要求,我们可以拿出系统本有的widget来魔改成我们自己的,速度而又有格调,毕竟男人不能说不行啊!

先看图,图一为系统的效果,图二为我们需要实现的,比较简单,这个当时是我们公司网页上的加载效果,需要用flutter_web来现实,着急直接改的系统的控件,话说魔改了好几个了,但是都是说用简单的图片和切换状态就可以实现的,都没用上,好遗憾

图一
图二
因为是录屏转gif,效果一般,请担待。

步骤

  1. 分析源码,系统的CircularProgressIndicator也是通过继承CustomPainter来画出来的,下面是关键的代码
//总的来说就是使用drawArc来画出图形,然后使用动画来更改初始角度和扇形
@override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = valueColor
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke;
    if (backgroundColor != null) {
      final Paint backgroundPaint = Paint()
        ..color = backgroundColor
        ..strokeWidth = strokeWidth
        ..style = PaintingStyle.stroke;
      canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint);
    }

    if (value == null) // Indeterminate
      paint.strokeCap = StrokeCap.square;
    canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint);
  }
  1. 分析目标结构:看到上图,我们目标就是四个圆点在转动,那好,我们就先画出四个圆点。
@override
  void paint(Canvas canvas, Size size) {
    valueColorPaint = Paint()
      ..color = valueColor
      ..style = PaintingStyle.fill;

    if (backgroundColor != null) {
      valueColorPaint = Paint()
        ..color = backgroundColor
        ..style = PaintingStyle.fill;
    }

    if (oneLeadColor != null) {
      oneLeadCirclePaint = Paint()
        ..color = oneLeadColor
        ..style = PaintingStyle.fill;
    } else {
      oneLeadCirclePaint = valueColorPaint;
    }

    double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
    double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);

    double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180);
    double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180);

    double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180);
    double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180);

    double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180);
    double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180);

    canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint);
    canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint);
    canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint);
    canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint);
  }

其中使用了三个Paint,分别来区分颜色,因为我们是来画圆的,就需要确定圆心坐标,最关键的代码就是这个了

double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);

来,不要客气,先看张图Flutter小技巧之魔改widget_第1张图片
正方形为辅助图形,确定初始位置4个圆的位置,这个时候圆的位置是不动的,然后圆心的坐标为(x,y),位置已经确认,是时候让小圆动起来了,而小圆的圆心移动路径就是图中的那个大圆了,逻辑图示神马的都已经分析好,剩下的就都是数学计算了,我们是以正方形的一半为基础来进行计算的,而圆心的移动轨迹就是大圆半径扫过的轨迹,以大圆半径扫过的角度为变量,就可以轻松让小圆动起来,所以第一象限圆的圆心坐标就是(arcSweepOne是大圆扫过的角度)

double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);

然后结合Tween动画,动态的把变化的坐标传进去,小圆的位置就会实时改变啦
看一下完整代码,就不贴GitHub了,就是一个简单的小例子。

import 'dart:math' as math;

import 'package:flutter/material.dart';

const double _kMinCircularProgressIndicatorSize = 8.0;

class FlowerLoadingIndicator extends ProgressIndicator {
  /// Creates a Flower progress indicator.
  ///
  /// {@macro flutter.material.progressIndicator.parameters}
  const FlowerLoadingIndicator({
    Key key,
    double value,
    Color backgroundColor,
    Animation valueColor,
    this.oneLeadColor,
    this.milliseconds,
    String semanticsLabel,
    String semanticsValue,
  }) : super(
          key: key,
          value: value,
          backgroundColor: backgroundColor,
          valueColor: valueColor,
          semanticsLabel: semanticsLabel,
          semanticsValue: semanticsValue,
        );

  final Color oneLeadColor;
  final int milliseconds;

  Color getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
  Color getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
  Color getOneLeadColor(BuildContext context) => oneLeadColor ?? Theme.of(context).primaryColorLight;

  Widget _buildSemanticsWrapper({
    @required BuildContext context,
    @required Widget child,
  }) {
    String expandedSemanticsValue = semanticsValue;
    if (value != null) {
      expandedSemanticsValue ??= '${(value * 100).round()}%';
    }
    return Semantics(
      label: semanticsLabel,
      value: expandedSemanticsValue,
      child: child,
    );
  }

  @override
  State createState() => _FlowerLoadingIndicator();
}

final Tween _kRotationTweenOne = new Tween(begin: 0.0, end: 360.0);
final Tween _kRotationTweenTwo = new Tween(begin: -90.0, end: 270.0);
final Tween _kRotationTweenThree = new Tween(begin: 90, end: 450.0);
final Tween _kRotationTweenFour = new Tween(begin: 180, end: 540.0);

class _FlowerLoadingIndicator extends State with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: widget.milliseconds ?? 1500),
      vsync: this,
    );
    _controller.repeat();
  }

  @override
  void didUpdateWidget(FlowerLoadingIndicator oldWidget) {
    super.didUpdateWidget(oldWidget);
    _controller.repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return _buildAnimation();
  }

  Widget _buildIndicator(BuildContext context, double arcSweepOne, double arcSweepTwo, double arcSweepThree, double arcSweepFour) {
    return widget._buildSemanticsWrapper(
      context: context,
      child: Container(
        constraints: const BoxConstraints(
          minWidth: _kMinCircularProgressIndicatorSize,
          minHeight: _kMinCircularProgressIndicatorSize,
        ),
        child: CustomPaint(
          painter: _FlowerLoadingIndicatorPainter(
            backgroundColor: widget.backgroundColor,
            valueColor: widget.getValueColor(context),
            oneLeadColor: widget.getOneLeadColor(context),
            arcSweepOne: arcSweepOne,
            arcSweepTwo: arcSweepTwo,
            arcSweepThree: arcSweepThree,
            arcSweepFour: arcSweepFour,
          ),
        ),
      ),
    );
  }

  Widget _buildAnimation() {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, Widget child) {
        return _buildIndicator(
          context,
          _kRotationTweenOne.evaluate(_controller),
          _kRotationTweenTwo.evaluate(_controller),
          _kRotationTweenThree.evaluate(_controller),
          _kRotationTweenFour.evaluate(_controller),
        );
      },
    );
  }
}

class _FlowerLoadingIndicatorPainter extends CustomPainter {
  _FlowerLoadingIndicatorPainter({
    this.backgroundColor,
    this.valueColor,
    this.arcSweepOne,
    this.arcSweepTwo,
    this.arcSweepThree,
    this.arcSweepFour,
    this.oneLeadColor,
  });

  final Color backgroundColor;
  final Color valueColor;
  final Color oneLeadColor;
  final double arcSweepOne;
  final double arcSweepTwo;
  final double arcSweepThree;
  final double arcSweepFour;
  Paint valueColorPaint;
  Paint oneLeadCirclePaint;

  @override
  void paint(Canvas canvas, Size size) {
    valueColorPaint = Paint()
      ..color = valueColor
      ..style = PaintingStyle.fill;

    if (backgroundColor != null) {
      valueColorPaint = Paint()
        ..color = backgroundColor
        ..style = PaintingStyle.fill;
    }

    if (oneLeadColor != null) {
      oneLeadCirclePaint = Paint()
        ..color = oneLeadColor
        ..style = PaintingStyle.fill;
    } else {
      oneLeadCirclePaint = valueColorPaint;
    }

    double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
    double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);

    double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180);
    double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180);

    double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180);
    double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180);

    double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180);
    double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180);

    canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint);
    canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint);
    canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint);
    canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint);
  }

  @override
  bool shouldRepaint(_FlowerLoadingIndicatorPainter oldPainter) {
    return true;
  }
}

拼搏在技术道路上的一只小白And成长之路

你可能感兴趣的:(Flutter,custom,widget,CsutomPainter)