美文网首页
聊一聊Flutter自定义组件

聊一聊Flutter自定义组件

作者: BlueSocks | 来源:发表于2023-07-06 15:46 被阅读0次

自定义组件介绍

  • 何时需要自定义组件
    • Flutter提供的现有组件无法满足业务需求,或者需要封装一些通用组件,多处复用时
  • Flutter中自定义组件有三种方式:
    • 通过组合其它组件
    • 自绘组件
    • 实现RenderObject

自定义组件类型

  • 组合其它Widget
    • 该方式通过拼装其它组件来组合成一个新的组件。
  • 自绘组件
    • 若遇到无法通过现有的组件来实现需要的UI时,可以通过自绘组件的方式来实现,例如一个颜色渐变的圆形进度条。
  • 实现RenderObject
    • Flutter提供的自身具有UI外观的组件,如TextImage都是通过相应的RenderObject渲染出来的。

自绘组件

  • 对于一些复杂或不规则的UI,可能无法通过组合其它组件的方式来实现
    • 比如需要一个渐变的圆形进度条、一个棋盘等。
  • 几乎所有的UI系统都提供一个自绘UI的接口,该接口通常会提供一块2D画布Canvas
    • Canvas内部封装一些基本绘制的API,开发者可以通过Canvas绘制各种自定义图形。
    • 在Flutter中,提供了一个CustomPaint 组件,可结合画笔CustomPainter来实现自定义图形绘制。

CustomPaint类

  • CustomPaint构造函数:
    CustomPaint({
      Key key,
      this.painter, 
      this.foregroundPainter,
      this.size = Size.zero, 
      this.isComplex = false, 
      this.willChange = false, 
      Widget child, //子节点,可以为空
    })
    
  • 阐述一下参数含义
    • painter: 背景画笔,会显示在子节点后面;
    • foregroundPainter: 前景画笔,会显示在子节点前面
    • size:当child为null时,代表默认绘制区域大小,如果有child则忽略此参数,画布尺寸则为child尺寸。如果有child但是想指定画布为特定大小,可以使用SizeBox包裹CustomPaint实现。
    • isComplex:是否复杂的绘制,如果是,Flutter会应用一些缓存策略来减少重复渲染的开销。
    • willChange:和isComplex配合使用,当启用缓存时,该属性代表在下一帧中绘制是否会改变。

示例:五子棋盘

  • 下面是五子棋游戏中棋盘和棋子的绘制来演示自绘UI的过程:
    import 'package:flutter/material.dart';
    import 'dart:math';
    
    class CustomPaintRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: CustomPaint(
            size: Size(300, 300), //指定画布大小
            painter: MyPainter(),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        double eWidth = size.width / 15;
        double eHeight = size.height / 15;
          
        //画棋盘背景
        var paint = Paint()
          ..isAntiAlias = true
          ..style = PaintingStyle.fill //填充
          ..color = Color(0x77cdb175); //背景为纸黄色
        canvas.drawRect(Offset.zero & size, paint);
    
        //画棋盘网格
        paint
          ..style = PaintingStyle.stroke //线
          ..color = Colors.black87
          ..strokeWidth = 1.0;
    
        for (int i = 0; i <= 15; ++i) {
          double dy = eHeight * i;
          canvas.drawLine(Offset(0, dy), Offset(size.width, dy), paint);
        }
    
        for (int i = 0; i <= 15; ++i) {
          double dx = eWidth * i;
          canvas.drawLine(Offset(dx, 0), Offset(dx, size.height), paint);
        }
    
        //画一个黑子
        paint
          ..style = PaintingStyle.fill
          ..color = Colors.black;
        canvas.drawCircle(
          Offset(size.width / 2 - eWidth / 2, size.height / 2 - eHeight / 2),
          min(eWidth / 2, eHeight / 2) - 2,
          paint,
        );
          
        //画一个白子
        paint.color = Colors.white;
        canvas.drawCircle(
          Offset(size.width / 2 + eWidth / 2, size.height / 2 - eHeight / 2),
          min(eWidth / 2, eHeight / 2) - 2,
          paint,
        );
      }
    
      //在实际场景中正确利用此回调可以避免重绘开销,本示例我们简单的返回true
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => true;
    }
    

绘制性能问题

  • 在实现自绘控件时应该考虑到性能开销,下面是关于性能优化的建议:
    • 利用好shouldRepaint返回值;在UI树重新build时,控件在绘制前都会先调用该方法以确定是否有必要重绘;若绘制的UI不依赖外部状态,就应该始终返回false,因为外部状态改变导致重新build时不会影响UI外观;
    • 若绘制依赖外部状态,则应该在shouldRepaint中判断依赖的状态是否改变,如果已改变则应返回true来重绘,反之则应返回false不需要重绘。
    • 绘制尽可能多的分层;在上面五子棋示例中,将棋盘和棋子的绘制放在了一起,这样会有一个问题:由于棋盘始终是不变的,用户每次落子时变的只是棋子,但是如果按照上面的代码来实现,每次绘制棋子时都要重新绘制一次棋盘,是没必要的。优化的方法就是将棋盘单独抽为一个组件,并设置其shouldRepaint回调值为false,将棋盘组件作为背景。将棋子的绘制放到另一个组件中,这样每次落子时只需要绘制棋子。
  • 自绘控件理论上可以实现任何2D图形外观
    • Flutter提供的所有组件最终都是通过调用Canvas绘制出来的,只不过绘制的逻辑被封装起来。

相关文章

  • Flutter开发上手,自定义组件 Button Demo

    在实际开发中,如果 Flutter 现有的组件不能满足我们的需求,那么就需要自定义组件。Flutter中自定义组件...

  • 来聊一聊ViewModel组件

    省略相关背景介绍,直接进入主题。ViewModel 它是一个对Activity/Fragment 组件进行数据管理...

  • 来聊一聊Lifecycle组件

    这里省略了Lifecycle的相关背景介绍,直接进入主题,不多逼逼。我们这里先不深入到源码分析,就从常用的几个类分...

  • 来聊一聊LiveData组件

    省略相关背景介绍,直接进入主题,LiveData看名字就可以知道,这是一个可以感知生命周期的数据模型。那么前面分析...

  • flutter封装AppBar

    flutter封装AppBar 最近做flutter项目,为了更简洁,封装了AppBar组件,不过不是自定义组件哈...

  • Flutter 常用按钮总结

    博客地址:flutterall.com 本文我们就一起聊一聊Flutter中的Button。由于Flutter是跨...

  • 聊一聊React高阶组件(HOC)

    写在前面(说点废话) 作为一个react开发者,提起高阶组价,我想并不陌生,即使是没听过,我相信,你毫无察觉的用过...

  • Flutter(三) 自定义组件、MaterialApp和Sca

    1. 自定义组件 在flutter中自定义组件就是自定义一个类,这个类需要继承StatelessWidget/St...

  • Flutter布局实战之GridView

    Flutter之所以有这么多组件,是因为在适当的位置需要用适当的组件。此篇主要聊下GrideView。想了解Gri...

  • 01.Flutter自定义Widget、Center组件、Tex

    1.Flutter 第一个 Demo Center 组件的使用 在 Flutter 中自定义组件其实就是一个类,这...

网友评论

      本文标题:聊一聊Flutter自定义组件

      本文链接:https://www.haomeiwen.com/subject/jknludtx.html