如何在Flutter中为容器小部件自定义/旋转BoxDecoration?

时间:2018-07-03 17:29:58

标签: dart flutter

我有一个小工具,可为公交车站建立圆形的个人资料图片,截至目前,它具有围绕个人资料图片的圆形边框。我想更改为虚线的圆形边框,并通过以虚线形式在个人资料图片上画圈/旋转来设置动画。有没有简单的方法可以做到这一点?非常感谢您提供的任何帮助!

circular profile picture

return new Transform(
  transform: new Matrix4.diagonal3Values(
    animation.avatarSize.value,
    animation.avatarSize.value,
    1.0,
  ),
  alignment: Alignment.center,
  child: new Container(
    width: 110.0,
    height: 110.0,
    decoration: new BoxDecoration(
      shape: BoxShape.circle,
      border: new Border.all(
        color: Colors.white30,
      ),
    ),
    margin: const EdgeInsets.only(
      top: 24.0,
      left: 16.0,
    ),
    padding: const EdgeInsets.all(3.0),
    child: new ClipOval(
      child: new Image.asset(
        stopName.avatar,
      ),
    ),
  ),
);

1 个答案:

答案 0 :(得分:2)

不幸的是,简单答案是没有简单的方法可以做到这一点。颤抖的人以其无穷的智慧得出的结论是,虚线的性能不足以将其包含在颤振中,因此,没人会画出虚线。 (是的,这句话的逻辑上的不连续是故意的。不要误会我的意思-我喜欢扑扑,开发人员做得很好,但是他们似乎确实是根据性能而不是功能做出了一些半任意的决定。 )。

我所见的原因解释是,基本上C ++版本将执行与dart代码类似的操作(除非直接在GPU上完成),因此他们希望有人最终在dart中实现虚线(可能作为图书馆的一部分?)。有关进展和讨论,请参见this github bug。如果以后希望看到破折号,请+1。如果有足够的人这样做,那么最终扑朔迷离的人可能会决定实施它。

但是目前,这意味着没有简单的方法可以用虚线制作CircleBorder或任何其他类型的边框。

但是,只要付出最大的努力,就可以实现您想要的目标=)。以下是为您提供所需功能的快速代码。请注意-它不是非常优化的,并且可能有更简单的方法来执行此操作,并且您可能会使用装饰并在那里进行油漆等操作……但这确实可行。

import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: new SafeArea(
        child: Column(
          children: [
            new DashedCircle(
              child: new ClippedDrawing(),
            )
          ],
        ),
      ),
    );
  }
}

class ClippedDrawing extends StatelessWidget {
  @override
  Widget build(BuildContext context) => new ClipOval(
        child: new Container(
          color: Colors.red,
        ),
      );
}

class DashedCircle extends StatefulWidget {
  final Widget child;

  const DashedCircle({Key key, this.child}) : super(key: key);

  @override
  DashedBorderState createState() => new DashedBorderState();
}

class DashedBorderState extends State<DashedCircle> with TickerProviderStateMixin<DashedCircle> {
  AnimationController controller;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = new AnimationController(vsync: this, duration: Duration(seconds: 10));
    animation = new Tween(begin: 0.0, end: pi * 2.0).animate(controller);
    controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        return new CustomPaint(
          painter: OvalPainter(
              color: Colors.blue, borderWidth: 1.0, dashLength: 5.0, spaceLength: 2.0, offset: animation.value),
          child: child,
        );
      },
      child: Container(
        width: 110.0,
        height: 110.0,
        padding: EdgeInsets.all(3.0),
        child: widget.child,
      ),
    );
  }
}

class OvalPainter extends CustomPainter {
  final Color color;
  final double borderWidth;
  final double dashLength;
  final double spaceLength;
  final double offset;

  OvalPainter(
      {@required this.borderWidth,
      @required this.dashLength,
      @required this.spaceLength,
      @required this.offset,
      @required this.color});

  double lastShortestSide;
  double lastDashLength;
  double lastSpaceLength;

  Path lastPath;

  @override
  void paint(Canvas canvas, Size size) {
    Rect rect = Offset.zero & size;

    var radius = rect.shortestSide / 2;

    canvas.translate(radius, radius);
    canvas.rotate(offset);

    Path path;
    if (lastShortestSide == rect.shortestSide &&
        dashLength == lastDashLength &&
        spaceLength == lastSpaceLength &&
        lastPath != null) {
      path = lastPath;
    } else {
      path = _getDashedCircularPath(rect.shortestSide / 2, dashLength, spaceLength);
      lastPath = path;
      lastShortestSide = rect.shortestSide;
      lastDashLength = dashLength;
      lastSpaceLength = spaceLength;
    }

    canvas.drawPath(
      path,
      new Paint()
        ..style = PaintingStyle.stroke
        ..color = color
        ..strokeWidth = borderWidth,
    );
  }

  @override
  bool shouldRepaint(OvalPainter oldDelegate) {
    return offset != oldDelegate.offset ||
        color != oldDelegate.color ||
        borderWidth != oldDelegate.borderWidth ||
        dashLength != oldDelegate.dashLength ||
        spaceLength != oldDelegate.spaceLength;
  }

  static Path _getDashedCircularPathFromNumber(double radius, int numSections, double dashPercentage) {
    var tau = 2 * pi;
    var actualTotalLength = tau / numSections;
    var actualDashLength = actualTotalLength * dashPercentage;

    double offset = 0.0;
    Rect rect = new Rect.fromCircle(center: Offset.zero, radius: radius);

    Path path = new Path();
    for (int i = 0; i < numSections; ++i) {
      path.arcTo(rect, offset, actualDashLength, true);
      offset += actualTotalLength;
    }

    return path;
  }

  static Path _getDashedCircularPath(double radius, double dashLength, double spaceLength) {
    // first, find number of radians that dashlength + spacelength take
    var tau = 2 * pi;
    var circumference = radius * tau;
    var dashSpaceLength = dashLength + spaceLength;
    var num = circumference / (dashSpaceLength);
    // we'll floor the value so that it's close-ish to the same amount as requested but
    // instead will be the same all around
    var closeNum = num.floor();

    return _getDashedCircularPathFromNumber(radius, closeNum, dashLength / dashSpaceLength);
  }
}