flutter는 canvas로 테두리를 그리고 애니메이션 효과를 실현합니다

코드는 다음과 같습니다 RRect를 이용하여radius가 있는 상자와 경계선을 그립니다.Path를 이용하여 경계선 궤적을 설정하고 PathMetric와AnimationController를 이용하여 궤적 애니메이션 효과를 달성합니다.
import 'dart:ui';

import 'package:flutter/material.dart';

class CutDownBorderButton extends StatefulWidget {
  const CutDownBorderButton(
      {Key? key,
      this.radius,
      required this.width,
      required this.height,
      required this.cutController,
      this.initBorderColor,
      this.activeBorderColor,
      this.borderWidth})
      : super(key: key);

  @override
  _CutWidgetState createState() => _CutWidgetState();

  final double? radius;
  final double width;
  final double height;
  final Color? initBorderColor;
  final Color? activeBorderColor;
  final double? borderWidth;
  final CutController cutController;
}

class _CutWidgetState extends State {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return CustomSingleChildLayout(
        delegate: _SingleChildLayoutDelegate(Size(widget.width, widget.height)),
        child: CustomPaint(
            painter: BorderPainter(
                progress: widget.cutController.value /
                    widget.cutController.duration!.inSeconds,
                radius: widget.radius,
                width: widget.width,
                height: widget.height,
                borderWidth: widget.borderWidth,
                initBorderColor: widget.initBorderColor,
                activeBorderColor: widget.activeBorderColor,
                boxColor: Colors.amber)));
  }
}

class _SingleChildLayoutDelegate extends SingleChildLayoutDelegate {
  final Size size;

  _SingleChildLayoutDelegate(this.size);

  @override
  Size getSize(BoxConstraints constraints) {
    return size;
  }

  @override
  bool shouldRelayout(_SingleChildLayoutDelegate oldDelegate) {
    return this.size != oldDelegate.size;
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return BoxConstraints.tight(size);
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return Offset((size.width - childSize.width) / 2,
        (size.height - childSize.height) / 2);
  }
}

class BorderPainter extends CustomPainter {
  BorderPainter(
      {required this.width,
      required this.height,
      required this.progress,
      required this.boxColor,
      this.initBorderColor,
      this.activeBorderColor,
      this.radius,
      this.borderWidth});

  final double? radius;
  final double? borderWidth;
  final double width;
  final double height;
  final double progress;
  final Color boxColor;
  final Color? initBorderColor;
  final Color? activeBorderColor;

  @override
  void paint(Canvas canvas, Size size) {
    Rect rect = Rect.fromCenter(
        center: Offset(width / 2, height / 2), width: width, height: height);
    RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(radius!));
    Paint paintRect = Paint()
      ..color = boxColor
      ..style = PaintingStyle.fill;
    canvas.drawRRect(rRect, paintRect);
    canvas.save();

    Paint initBorder = Paint()
      ..color = initBorderColor ?? Colors.red
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth ?? 5;

    Paint activeBorder = Paint()
      ..color = activeBorderColor ?? Colors.grey
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth ?? 5;

    Path path = Path()
      ..moveTo(width / 2, 0)
      ..relativeLineTo(width / 2 - radius!, 0)
      ..relativeArcToPoint(Offset(radius!, radius!),
          radius: Radius.circular(radius!))
      ..relativeLineTo(0, height - radius! * 2)
      ..relativeArcToPoint(Offset(-radius!, radius!),
          radius: Radius.circular(radius!))
      ..relativeLineTo(-width + radius! * 2, 0)
      ..relativeArcToPoint(Offset(-radius!, -radius!),
          radius: Radius.circular(radius!))
      ..relativeLineTo(0, -height + radius! * 2)
      ..relativeArcToPoint(Offset(radius!, -radius!),
          radius: Radius.circular(radius!))
      ..relativeLineTo(width / 2 - radius!, 0)
      ..close();

    canvas.drawPath(path, initBorder);
    path.addRRect(rRect);
    PathMetrics pathMetrics = path.computeMetrics();
    PathMetric pathMetric = pathMetrics.first;
    Path extPath = pathMetric.extractPath(0, pathMetric.length * progress);
    canvas.drawPath(extPath, activeBorder);
    canvas.restore();
  }

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

class CutController extends AnimationController {
  CutController({required TickerProvider vsync, int durationInt = 30})
      : super(
            reverseDuration: Duration(milliseconds: durationInt * 1000),
            duration: Duration(milliseconds: durationInt * 1000),
            lowerBound: 0,
            upperBound: durationInt.toDouble(),
            vsync: vsync);
}


사용 예:
import 'package:flitter_okgo/cut_down_border_button.dart';
import 'package:flitter_okgo/hand_written_board.dart';
import 'package:flutter/material.dart';

class CutDownBorderPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State
    with SingleTickerProviderStateMixin {
  late CutController controller = CutController(vsync: this, durationInt: 3);

  @override
  void initState() {
    super.initState();
    controller.addListener(() {
      setState(() {});
    });
  }

  Widget get initFloatingActionButton {
    return FloatingActionButton(
      backgroundColor: Colors.grey,
      elevation: 1,
      focusElevation: 1,
      onPressed: () {
        controller.forward();
      },
      child: Icon(Icons.android),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      floatingActionButton: initFloatingActionButton,
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      body: Center(
          child: Stack(
        children: [
          CutDownBorderButton(
            cutController: controller,
            radius: 25,
            width: 100,
            height: 100,
          ),
          Container(
            width: 100,
            height: 100,
            child: Center(
              child: Text('${controller.value.round()}'),
            ),
          ),
        ],
      )),
    );
  }
}

좋은 웹페이지 즐겨찾기