前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter自定义组件最佳实践

flutter自定义组件最佳实践

原创
作者头像
brzhang
发布2024-04-02 13:01:02
2101
发布2024-04-02 13:01:02
举报
文章被收录于专栏:玩转全栈玩转全栈玩转全栈

接触flutter一段时间,用flutter做过一些demo项目,也看了一些flutter的源码,对flutter的组件体系有了一些了解,这里总结一下flutter自定义组件的最佳实践。

在flutter上开发自定义组件,实际上有两种方式,一种是继承StatelessWidgetStatefulWidget,另一种是使用RenderObject。继承StatelessWidgetStatefulWidget是最常用的方式,因为它们提供了一些现成的方法和属性,可以方便地实现组件的生命周期和状态管理。而使用RenderObject则需要自己实现一些方法和属性,比较复杂,一般用于实现一些复杂的自定义组件。

我们来分别看看这两种方式的实现。

1. 继承StatelessWidgetStatefulWidget

继承StatelessWidgetStatefulWidget是最常用的方式,因为它们提供了一些现成的方法和属性,可以方便地实现组件的生命周期和状态管理。下面是一个简单的例子,实现一个Counter组件,这个组件可以显示一个计数器,用户可以点击按钮来增加计数器的值。

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

在这个例子中,我们定义了一个Counter组件,它继承自StatefulWidget,并实现了一个_CounterState类,这个类继承自State,用来管理组件的状态。在_CounterState类中,我们定义了一个_count变量来保存计数器的值,以及一个_increment方法来增加计数器的值。在build方法中,我们使用Column组件来显示计数器的值和一个按钮,用户可以点击按钮来增加计数器的值。

2. 使用RenderObject

使用RenderObject是一种更底层的方式,它可以让我们更加灵活地控制组件的布局和绘制。下面是一个简单的例子,实现一个钟表组件,这个组件可以显示当前时间。

import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';

class Clock extends SingleChildRenderObjectWidget {
  Clock({Widget child}) : super(child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderClock();
  }
}

class RenderClock extends RenderBox {
  DateTime _dateTime = DateTime.now();
  Timer _timer;

  RenderClock() {
    _timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
      _dateTime = DateTime.now();
      this.markNeedsPaint();
    });
  }

  @override
  void performLayout() {
    size = Size(200, 200);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final Paint paint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    final center = size.center(offset);
    final radius = size.shortestSide / 2;

    context.canvas.drawCircle(center, radius, paint);

    // Draw the hour hand.
    final hourHandLength = radius * 0.5;
    final hourHandRadians = ((_dateTime.hour % 12) + _dateTime.minute / 60) * 2 * math.pi / 12 - math.pi / 2;
    context.canvas.drawLine(
      center,
      Offset(center.dx + hourHandLength * math.cos(hourHandRadians),
             center.dy + hourHandLength * math.sin(hourHandRadians)),
      paint..strokeWidth = 6.0,
    );

    // Draw the minute hand.
    final minuteHandLength = radius * 0.8;
    final minuteHandRadians = (_dateTime.minute + _dateTime.second / 60) * 2 * math.pi / 60 - math.pi / 2;
    context.canvas.drawLine(
      center,
      Offset(center.dx + minuteHandLength * math.cos(minuteHandRadians),
             center.dy + minuteHandLength * math.sin(minuteHandRadians)),
      paint..strokeWidth = 4.0,
    );

    // Draw the second hand.
    final secondHandLength = radius * 0.9;
    final secondHandRadians = _dateTime.second * 2 * math.pi / 60 - math.pi / 2;
    context.canvas.drawLine(
      center,
      Offset(center.dx + secondHandLength * math.cos(secondHandRadians),
             center.dy + secondHandLength * math.sin(secondHandRadians)),
      paint..color = Colors.red..strokeWidth = 2.0,
    );
  }

  @override
  void detach() {
    _timer.cancel();
    super.detach();
  }
}

效果如下图所示:

图 0
图 0

图 0

上面给出了两种方式实现自定义组件的例子,第一种方式是继承StatelessWidgetStatefulWidget,第二种方式是使用RenderObject。在实际开发中,我们可能需要遵循一些最佳实践,来提高组件的性能和可维护性。这里主要讲一下组件的封装、布局和文档吧。

1. 组件的封装

在flutter中,组件的封装是常有的是,虽然说大部分时候flutter的组件库已经提供了我们需要的组件,但是有时候我们还是需要自定义一些组件来满足我们的需求。在封装组件时,我们应该遵循以下几个原则:

  • 单一职责原则:一个组件应该只负责一项功能,不要将多个功能耦合在一个组件中。
  • 高内聚低耦合:组件内部的各个部分之间应该高度内聚,不同组件之间应该低耦合。
  • 易用性:组件的使用应该尽可能简单,不要让用户花费过多的时间和精力来学习如何使用组件。
  • 可定制性:组件应该具有一定的可定制性,可以根据用户的需求进行定制。
  • 易扩展性:组件应该具有一定的扩展性,可以方便地扩展新的功能。

下面,我们来一一个简单的例子,比如,我们要实现一个日历组件,这个日历组件可以显示当前月份的日历,并且可以选择日期。我们可以将这个日历组件封装成一个Calendar组件,这个组件可以接受一个DateTime类型的参数,用来指定当前月份的日期。这个Calendar组件可以包含一个MonthView组件和一个WeekView组件,MonthView组件用来显示当前月份的日历,WeekView组件用来显示星期几。这样,我们就将日历组件封装成了一个Calendar组件,用户只需要传入一个DateTime类型的参数,就可以使用这个日历组件了。让我来看看,如何遵循上面的原则来封装这个日历组件。

class Calendar extends StatelessWidget {
  final DateTime date;

  Calendar({this.date});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MonthView(date: date),
        WeekView(),
      ],
    );
  }
}

class MonthView extends StatelessWidget {
  final DateTime date;

  MonthView({this.date});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text('Month View'),
    );
  }
}

class WeekView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text('Week View'),
    );
  }
}

这个例子我们来看看以上原则的应用:单一职责原则(Calendar、MonthView和WeekView各自负责一项功能)、高内聚低耦合(Calendar组件由MonthView和WeekView组成,但它们之间的耦合度很低)、易用性(使用Calendar组件只需要传入一个DateTime参数)、可定制性(可以通过修改MonthView和WeekView的实现来定制组件的表现)和易扩展性(可以通过添加更多的子组件来扩展Calendar组件的功能)。

2. 组件的布局

一个好的布局可以提高组件的性能和用户体验,有些组件在涉及之初就需要考虑响应式布局,这样可以适应不同的屏幕尺寸和分辨率。在布局组件时,我们应该遵循以下几个原则:

  • 灵活性:组件的布局应该具有一定的灵活性,可以适应不同的屏幕尺寸和分辨率。
  • 性能:组件的布局应该尽可能简单,不要包含过多的嵌套和无用的布局。
  • 响应式:组件的布局应该具有一定的响应式,可以根据屏幕尺寸和分辨率进行自适应。
  • 可访问性:组件的布局应该具有一定的可访问性,可以方便地让用户进行交互。

下面,我们就来看看另外一个另外一个例子,现在大家都喜欢玩大语言模型聊天对话应用,又要支持图片又要支持文字,我们可以封装一个ChatMessage组件,这个组件可以显示用户发送的消息,可以是文字也可以是图片。这个ChatMessage组件可以包含一个TextMessage组件和一个ImageMessage组件,TextMessage组件用来显示文字消息,ImageMessage组件用来显示图片消息。这样,我们就将聊天消息组件封装成了一个ChatMessage组件,用户只需要传入一个Message对象,就可以使用这个聊天消息组件了。让我来看看,如何遵循上面的原则来布局这个聊天消息组件。

class ChatMessage extends StatelessWidget {
  final Message message;

  ChatMessage({this.message});

  @override
  Widget build(BuildContext context) {
    return Flexible(
      child: message.type == MessageType.text
          ? TextMessage(text: message.text)
          : ImageMessage(image: message.image),
    );
  }
}

class TextMessage extends StatelessWidget {
  final String text;

  TextMessage({this.text});

  @override
  Widget build(BuildContext context) {
    return Semantics(
      child: Text(text),
      label: 'Text message',
    );
  }
}

class ImageMessage extends StatelessWidget {
  final String image;

  ImageMessage({this.image});

  @override
  Widget build(BuildContext context) {
    return Semantics(
      child: Image.network(image),
      label: 'Image message',
    );
  }
}

在这个例子中,ChatMessage组件使用了Flexible来自动调整其大小,以适应不同的屏幕尺寸和分辨率(灵活性和响应式)。同时,我们避免了不必要的嵌套和容器使用,以提高性能。另外,我们使用了Semantics组件来提供辅助功能,以提高可访问性。

3. 组件的文档

一个组件研发完成后,我们应该为组件编写文档,以便其他开发者能够快速了解组件的用法和功能。没有文档的组件是没有人敢用的,现在的开发者都是懒得,不想花费过多的时间和精力来学习如何使用组件。在编写组件文档时,我们应该遵循以下几个原则:

  • 简洁明了:文档应该简洁明了,不要包含过多的废话和无用的信息。
  • 示例代码:文档应该包含示例代码,以便开发者能够快速了解组件的用法。
  • 参数说明:文档应该包含参数说明,以便开发者能够了解组件的参数和功能。
  • 返回值说明:文档应该包含返回值说明,以便开发者能够了解组件的返回值和功能。
  • 效果展示:文档应该包含效果展示,以便开发者能够了解组件的效果和功能。

今天就扯这么多吧,希望对大家有所帮助。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 继承StatelessWidget或StatefulWidget
  • 2. 使用RenderObject
  • 1. 组件的封装
  • 2. 组件的布局
  • 3. 组件的文档
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档