前言

最近这段时刻,在看Flutter制作的相关常识。主要是看张风捷特烈的制作小册。经过对小册的学习了解Flutter制作的相关常识,见识到了绚丽多姿的Flutter制作世界。
此文是对制作小册内容的一个实践,经过一个自定义控件的案例,初步探索怎么进行Flutter自定义控件的开发。

话不多说,让咱们开端今天的制作之旅吧。

一,初始版别自定义控件效果图:

Flutter自定义控件初探

我把上述的控件命名为同心边框图形,顾名思义,该控件是由多个半径不同的圆形边框组成的自定义控件,且这些边框具有相同的中心点。

二,控件需求拆解:

经过拆解剖析,该控件的功用能够分为以下几点:

1,不同圆形的半径是等差递加的,即两两相邻的圆形距离是持平的。

2,支撑事务侧传入参数,来确认图形的色彩。

3,底部的两个Slider用来操控自定义控件的中心点。

4,支撑每秒切换圆形边框的色彩。

2.1 同心圆的制作

咱们先完结中心多个同心圆的制作, 很明显关于自定义控件咱们要自定义CustomPainter,然后拿到Canvas进行制作。关于同心圆来说,需求三个参数,即最小圆半径,最大圆半径和色彩数组。其中色彩数组还用来确认同心圆的数量

class ConcentricPainter extends CustomPainter {
  ConcentricPainter({this.minRadius = 10, this.maxRadius = 20, 
  this.colorsList = const [Colors.amber, Colors.cyan]});
  final double minRadius;
  final double maxRadius;
  List<Color> colorsList = [Colors.amber, Colors.cyan];
  final Paint mPaint = Paint()
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;
  @override
  void paint(Canvas canvas, Size size) {
    debugPrint("size.width and size.height=${size.width } ${size.height }");
    //将画布移到控件中心
    canvas.translate(size.width / 2, size.height / 2);
    Path path = Path();
    mPaint.color = colorsList[0];
    drawCircle(path, canvas, minRadius);
    if (colorsList.length > 2) {
      //被切割的同心圆份数
      int count = colorsList.length - 1;
      double divider = (maxRadius - minRadius) / count;
      //从1开端,由于0是minRadius
      for (int i = 1; i < count; i++) {
        path.reset();
        mPaint.color = colorsList[i];
        drawCircle(path, canvas, minRadius + i * divider);
      }
    }
    path.reset();
    mPaint.color = colorsList[colorsList.length - 1];
    drawCircle(path, canvas, maxRadius);
  }
  void drawCircle(Path path, Canvas canvas, double radius) {
    path.addOval(Rect.fromCenter(center: Offset.zero, width: radius, height: radius));
    canvas.drawPath(path, mPaint);
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

2.2 根据Slider来操控同心圆的中心点

调整同心圆的中心点,所以ConcentricPainter需求两个偏移量参数 centerFactorX和centerFactorY
由于需求Slider来操控中心点,所以需求创立一个State


class ConcentricPagerState extends State<ConcentricPager>{
  //省掉不重要代码
@override
Widget build(BuildContext context) {
  return LayoutBuilder(builder: (_, zone) {
    debugPrint("zone maxWidth and zone maxHeight ${zone.maxWidth} ${zone.maxHeight}");
    return Column(
      children: [buildPager(), buildSliderX(), buildSliderY()],
    );
  });
}
Widget buildSliderX() {
  return Padding(
      padding: const EdgeInsets.all(30),
      child: Slider(
          max: 180,
          min: -180,
          divisions: 360,
          value: _valueX,
          onChanged: (v) {
            setState(() {
              _valueX = v;
            });
          }));
}
Widget buildSliderY() {
  return Padding(
      padding: const EdgeInsets.all(10),
      child: Slider(
          max: 180,
          min: -180,
          divisions: 360,
          value: _valueY,
          onChanged: (v) {
            setState(() {
              _valueY = v;
            });
          }));
}
Widget buildPager() {
  return Container(
    width: 300,
    height: 200,
    color: Colors.white,
    alignment: Alignment.center,
    child: LayoutBuilder(builder: (_, zone) {
      return CustomPaint(
        size: Size(zone.maxWidth, zone.maxHeight),
        // 此刻centerFactorX的值在-11之间
        painter: ConcentricPainter(manage: manager,
            minRadius: widget.minRadius, maxRadius: widget.maxRadius, 
            centerFactorX: _valueX / 180, centerFactorY: _valueY / 180),
      );
    }),
  );
}
}
class ConcentricPainter extends CustomPainter {
ConcentricPainter({this.minRadius = 10, this.maxRadius = 20,
this.centerFactorX = 0, this.centerFactorY = 0, required this.manage})
: super(repaint: manage);
//省掉不重要代码
void drawCircle(Path path, Canvas canvas, double radius, Size size) {
  path.addOval(Rect.fromCenter
  (center: Offset.zero + Offset(size.width / 2 * centerFactorX, size.height / 2 * centerFactorY),   
  width: radius, height: radius));
  canvas.drawPath(path, mPaint);
}
}

到这里,一个能够调整中心点位置的同心圆控件就完结了

2.3 添加动画,动态改动边框色彩

前置常识,CustomPainter有一个类型为Listenable?的变量_repaint,Listenable是一个抽象类,一般其完成类是ChangeNotifier。当_repaint调用notifyListeners()时,CustomPainter就会重绘。
能够使用Ticker类对象能够完成16ms回调功用

class ConcentricManager with ChangeNotifier {
  late List<Color> colorsList;
  //原始色彩数组
  late List<Color> originalColorList;
  late DateTime datetime; // 用来上次更新时刻
  ConcentricManager(this.colorsList){
    //对colorList数组进行深复制
    originalColorList =  List<Color>.generate(
        colorsList.length,//要传入的长度,不能大于_categoryListModel.goods的长度,可根据实际需求设置
            (int index){//创立新的QualitySamplingGoodsModel,默认体系会主动帮咱们创立
          return colorsList[index];
        },growable: true);
    datetime = DateTime.now();
  }
  //每秒会回调一次 
  void tick(DateTime now) {
    randomColorList();
    notifyListeners();
  }
  void randomColorList(){
    int count = 0;
    int size = colorsList.length;
    Random random = Random();
    List<Color> tempList = [];
    //随机色彩改变
    colorsList.forEach((element) {
      int index = random.nextInt(size-1);
      debugPrint("index:${index}");
      tempList.add(originalColorList[index]);
      count++;
    });
    colorsList = tempList;
  }
}
class ConcentricPagerState extends State<ConcentricPager> with SingleTickerProviderStateMixin {
  double _valueX = 20.0;
  double _valueY = 20.0;
  late Ticker _ticker;
  late ConcentricManager manager;
  @override
  void initState(){
    super.initState();
    manager = ConcentricManager(widget.colorsList);
    _ticker = createTicker(_tick)
      ..start();
  }
  //省掉无关代码
void _tick(Duration elapsed) {
  DateTime now = DateTime.now();
  //1s更新一次
  if(now.millisecondsSinceEpoch - manager.datetime.millisecondsSinceEpoch > 1000){
    manager..datetime = now..tick(now);
  }
}

至此初始版别的自定义控件功用便完结了

三,功用扩展

现在咱们对此控件进行功用扩展,让它支撑同心方形边框的制作。 很明显方形同心边框和圆形同心边框只有制作细节不同,其他都相同。所以咱们能够把ConcentricPainter抽成抽象类,提供抽象方法让其子类完成

abstract class ConcentricPainter extends CustomPainter {
  final ConcentricManager manage;
  ConcentricPainter(
      {this.minRadius = 10,
        this.maxRadius = 20,
        this.centerFactorX = 0,
        this.centerFactorY = 0,
        required this.manage})
      : super(repaint: manage);
  final double minRadius;
  final double maxRadius;
  final double centerFactorX;
  final double centerFactorY;
  List<Color> colorsList = [Colors.amber, Colors.cyan];
  final Paint mPaint = Paint()
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;
  @override
  void paint(Canvas canvas, Size size) {
    debugPrint("size.width and size.height=${size.width} ${size.height}");
    canvas.translate(size.width / 2, size.height / 2);
    Path path = Path();
    colorsList = manage.colorsList;
    mPaint.color = colorsList[0];
    drawShape(path, canvas, minRadius, size);
    if (colorsList.length > 2) {
      //被切割的同心圆份数
      int count = colorsList.length - 1;
      double divider = (maxRadius - minRadius) / count;
      //从1开端,由于0是minRadius
      for (int i = 1; i < count; i++) {
        path.reset();
        mPaint.color = colorsList[i];
        drawShape(path, canvas, minRadius + i * divider, size);
      }
    }
    path.reset();
    mPaint.color = colorsList[colorsList.length - 1];
    drawShape(path, canvas, maxRadius, size);
  }
  void drawShape(Path path, Canvas canvas, double radius, Size size);
}
class ConcentricCirclePainter extends ConcentricPainter {
  ConcentricCirclePainter(
      {minRadius = 10,
        maxRadius = 20,
        centerFactorX = 0,
        centerFactorY = 0,
        required manage})
      : super(
      minRadius: minRadius,
      maxRadius: maxRadius,
      centerFactorX: centerFactorX,
      centerFactorY: centerFactorY,
      manage: manage);
  @override
  void drawShape(Path path, Canvas canvas, double radius, Size size) {
    path.addOval(Rect.fromCenter(
        center: Offset.zero +
            Offset(size.width / 2 * centerFactorX,
                size.height / 2 * centerFactorY),
        width: radius,
        height: radius));
    canvas.drawPath(path, mPaint);
  }
}
class ConcentricSquarePainter extends ConcentricPainter {
  ConcentricSquarePainter(
      {minRadius = 10,
        maxRadius = 20,
        centerFactorX = 0,
        centerFactorY = 0,
        required manage})
      : super(
      minRadius: minRadius,
      maxRadius: maxRadius,
      centerFactorX: centerFactorX,
      centerFactorY: centerFactorY,
      manage: manage);
  @override
  void drawShape(Path path, Canvas canvas, double radius, Size size) {
    path.addRect(Rect.fromCenter(
        center: Offset.zero +
            Offset(size.width / 2 * centerFactorX,
                size.height / 2 * centerFactorY),
        width: radius,
        height: radius));
    canvas.drawPath(path, mPaint);
  }
}

至此这个自定义控件的开发介绍就告一段落了。我们还能够在这个基础上开发更多功用,例如给控件添加淡入淡出的动画,或许扩展出更多不同图形的同心控件。学会了制作技巧,就能把自己的想法经过制作方法展现出来。希望我们能从此文中获益,然后敞开制作之旅,体会到制作的趣味。

本文Demo请点击

感谢

张风捷特烈的小册–Flutter 制作指南 – 妙笔生花
该小册详细记载了Flutter制作的相关常识,激发了我的制作兴趣,然后有了本文的诞生。

您若喜欢,请点赞、重视,您的鼓舞是我行进的动力