Flutter 彩邊圓角 Container 的實現

devLake發表於2021-08-05

本文大量致敬了張風捷特烈的文章。

在 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,
}

複製程式碼

相關文章