Flutter 彩边圆角 Container 的实现

1,663 阅读2分钟

本文大量致敬了张风捷特烈的文章。

在 Flutter 中,如果想要为 Container 的四个边分别设置不同的颜色,方法很简单:

Container(
  width: 200,
  height: 200,
  decoration: BoxDecoration(
    border: Border(
      left: BorderSide(color: Colors.red, width: 5),
      top: BorderSide(color: Colors.blue, width: 5),
      right: BorderSide(color: Colors.orange, width: 5),
      bottom: BorderSide(color: Colors.green, width: 5),
    ),
    // borderRadius: BorderRadius.circular(20),
  ),
  child: Center(
    child: Text(
      'Hello World',
      style: TextStyle(fontSize: 20),
    ),
  ),
),

实现效果: Screen Shot 2021-08-02 at 12.01.05 AM.png

边和边之间的过渡非常生硬,并且一旦想要吧 Container 设置为圆角,编译器就会报错:


======== Exception caught by rendering library =====================================================

The following assertion was thrown during paint():

A borderRadius can only be given for a uniform Border.

\


The following is not uniform:

BorderSide.color

The relevant error-causing widget was:

...

那么如何才能实现“彩边圆角“的 Container 呢?先来看一下我们最终实现的效果:

Screen Shot 2021-08-05 at 10.21.52 PM.png

首先我们要自己实现一个 Decoration 类:


class ColorDecoration extends Decoration {

  @override

  BoxPainter createBoxPainter([ui.VoidCallback? onChanged]) =>
      ColorBoxPainter();

}

IDE 提示我们要实现一个 createBoxPainter 方法,并且要返回一个 BoxPainter 类。所以我们再继承 BoxPainter 实现一个 ColorBoxPainter 类,并且实现 paint 方法。


class ColorBoxPainter extends BoxPainter {

  @override

  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {


  }

}

我们在该 Paint 方法中实现对containter 的绘制。


class ColorBoxPainter extends BoxPainter {

  @override

  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {

var colors = [
  Color(0xFFF60C0C),
  Color(0xFFF3B913),
  Color(0xFFE7F716),
  Color(0xFF3DF30B),
  Color(0xFF0DF6EF),
  Color(0xFF0829FB),
  Color(0xFFB709F4),
];

var pos = [1.0 / 7, 2.0 / 7, 3.0 / 7, 4.0 / 7, 5.0 / 7, 6.0 / 7, 1.0];

// 矩形中心位置
    var center_pos = Offset(
      offset.dx + configuration.size!.width / 2,
      offset.dy + configuration.size!.height / 2,
    );

// 矩形右下角位置
    var right_bottom_pos = Offset(
      offset.dx + configuration.size!.width,
      offset.dy + configuration.size!.height,
    );

// 定义 painter
    Paint painter = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth
      ..ui.Gradient.sweep(center_pos, colors, pos);

// 定义矩形
    var rect = Rect.fromCenter(
      center: center_pos,
      width: configuration.size!.width,
      height: configuration.size!.height,
    );

    var rrect = RRect.fromRectXY(rect, radius.x, radius.y);

// 绘画

    canvas.drawRRect(rrect, painter);

  }
}

实现这个之后,其实最根本的东西就已经做出来了,下面只需要在类的构造函数里加上几个参数,允许用户一定程度上进行自定义即可。

下面是全部代码:


import 'dart:ui' as ui;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // 确定初始化
  SystemChrome.setPreferredOrientations(// 使设备横屏显示
      [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
  SystemChrome.setEnabledSystemUIOverlays([]); // 全屏显示
  runApp(Paper());
}

class Paper extends StatelessWidget {
  final List<Color> colors = [
    Colors.red,
    Colors.orange,
    Colors.yellow,
    Colors.green,
    Colors.blue,
    Colors.indigo,
    Colors.purple
  ];

  @override
  Widget build(BuildContext context) {
    return MediaQuery(
      data: MediaQueryData(),
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: Container(
            width: 200,
            height: 200,
            decoration: ColorDecoration(
              colors: colors,
              radius: Radius.circular(5),
              gradientMethod: GradientMethod.sweep,
              borderWidth: 5,
            ),
            child: Center(
              child: Text(
                'Hello World',
                style: TextStyle(fontSize: 20),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class ColorDecoration extends Decoration {
  Radius radius;
  List<Color> colors;
  GradientMethod gradientMethod;
  double borderWidth;

  ColorDecoration(
      {required this.colors,
      required this.radius,
      required this.gradientMethod,
      required this.borderWidth});

  @override
  BoxPainter createBoxPainter([ui.VoidCallback? onChanged]) {
    return ColorBoxPainter(
      colors: colors,
      radius: this.radius,
      gradientMethod: this.gradientMethod,
      borderWidth: this.borderWidth,
    );
  }
}

class ColorBoxPainter extends BoxPainter {
  Radius radius;
  List<Color> colors;
  GradientMethod gradientMethod;
  double borderWidth;

  ColorBoxPainter(
      {required this.colors,
      required this.radius,
      required this.gradientMethod,
      required this.borderWidth});

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    var color_stop = List<double>.generate(
      colors.length,
      (index) => index / colors.length,
    );
    var center_pos = Offset(
      offset.dx + configuration.size!.width / 2,
      offset.dy + configuration.size!.height / 2,
    );
    var right_bottom_pos = Offset(
      offset.dx + configuration.size!.width,
      offset.dy + configuration.size!.height,
    );
    var border_len_average =
        (configuration.size!.height + configuration.size!.width) / 2;
    Paint painter = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth;
    if (gradientMethod == GradientMethod.sweep)
      painter.shader = ui.Gradient.sweep(center_pos, colors, color_stop);
    else if (gradientMethod == GradientMethod.liner)
      painter.shader =
          ui.Gradient.linear(offset, right_bottom_pos, colors, color_stop);
    else if (gradientMethod == GradientMethod.radial)
      painter.shader = ui.Gradient.radial(
          center_pos, border_len_average, colors, color_stop);
    else {
      throw Exception('Gradient Can NOT be Empty.');
    }

    var rect = Rect.fromCenter(
      center: center_pos,
      width: configuration.size!.width,
      height: configuration.size!.height,
    );
    var rrect = RRect.fromRectXY(rect, radius.x, radius.y);
    canvas.drawRRect(rrect, painter);
  }
}

enum GradientMethod {
  liner,
  sweep,
  radial,
}