Flutter核心技术与实战
陈航
美团点评高级技术专家
立即订阅
6150 人已学习
课程目录
已完结 47 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (3讲)
开篇词 | 为什么每一位大前端从业者都应该学习Flutter?
免费
01 | 预习篇 · 从0开始搭建Flutter工程环境
02 | 预习篇 · Dart语言概览
Flutter开发起步 (3讲)
03 | 深入理解跨平台方案的历史发展逻辑
04 | Flutter区别于其他方案的关键技术是什么?
05 | 从标准模板入手,体会Flutter代码是如何运行在原生系统上的
Dart语言基础 (3讲)
06 | 基础语法与类型变量:Dart是如何表示信息的?
07 | 函数、类与运算符:Dart是如何处理信息的?
08 | 综合案例:掌握Dart核心特性
Flutter基础 (13讲)
09 | Widget,构建Flutter界面的基石
10 | Widget中的State到底是什么?
11 | 提到生命周期,我们是在说什么?
12 | 经典控件(一):文本、图片和按钮在Flutter中怎么用?
13 | 经典控件(二):UITableView/ListView在Flutter中是什么?
14 | 经典布局:如何定义子控件在父容器中排版的位置?
15 | 组合与自绘,我该选用何种方式自定义Widget?
16 | 从夜间模式说起,如何定制不同风格的App主题?
17 | 依赖管理(一):图片、配置和字体在Flutter中怎么用?
18 | 依赖管理(二):第三方组件库在Flutter中要如何管理?
19 | 用户交互事件该如何响应?
20 | 关于跨组件传递数据,你只需要记住这三招
21 | 路由与导航,Flutter是这样实现页面切换的
Flutter进阶 (17讲)
22 | 如何构造炫酷的动画效果?
23 | 单线程模型怎么保证UI运行流畅?
24 | HTTP网络编程与JSON解析
25 | 本地存储与数据库的使用和优化
26 | 如何在Dart层兼容Android/iOS平台特定实现?(一)
27 | 如何在Dart层兼容Android/iOS平台特定实现?(二)
28 | 如何在原生应用中混编Flutter工程?
29 | 混合开发,该用何种方案管理导航栈?
30 | 为什么需要做状态管理,怎么做?
31 | 如何实现原生推送能力?
32 | 适配国际化,除了多语言我们还需要注意什么?
33 | 如何适配不同分辨率的手机屏幕?
34 | 如何理解Flutter的编译模式?
35 | Hot Reload是怎么做到的?
36 | 如何通过工具链优化开发调试效率?
37 | 如何检测并优化Flutter App的整体性能表现?
38 | 如何通过自动化测试提高交付质量?
Flutter综合应用 (6讲)
39 | 线上出现问题,该如何做好异常捕获与信息采集?
40 | 衡量Flutter App线上质量,我们需要关注这三个指标
41 | 组件化和平台化,该如何组织合理稳定的Flutter工程结构?
42 | 如何构建高效的Flutter App打包发布环境?
43 | 如何构建自己的Flutter混合开发框架(一)?
44 | 如何构建自己的Flutter混合开发框架(二)?
结束语 (1讲)
结束语 | 勿畏难,勿轻略
特别放送 (1讲)
特别放送 | 温故而知新,与你说说专栏的那些思考题
Flutter核心技术与实战
登录|注册

15 | 组合与自绘,我该选用何种方式自定义Widget?

陈航 2019-08-01
你好,我是陈航。
在上一次分享中,我们认识了 Flutter 中最常用也最经典的布局 Widget,即单子容器 Container、多子容器 Row/Column,以及层叠容器 Stack 与 Positioned,也学习了这些不同容器之间的摆放子 Widget 的布局规则,我们可以通过它们,来实现子控件的对齐、嵌套、层叠等,它们也是构建一个界面精美的 App 所必须的布局概念。
在实际开发中,我们会经常遇到一些复杂的 UI 需求,往往无法通过使用 Flutter 的基本 Widget,通过设置其属性参数来满足。这个时候,我们就需要针对特定的场景自定义 Widget 了。
在 Flutter 中,自定义 Widget 与其他平台类似:可以使用基本 Widget 组装成一个高级别的 Widget,也可以自己在画板上根据特殊需求来画界面。
接下来,我会分别与你介绍组合和自绘这两种自定义 Widget 的方式。

组装

使用组合的方式自定义 Widget,即通过我们之前介绍的布局方式,摆放项目所需要的基础 Widget,并在控件内部设置这些基础 Widget 的样式,从而组合成一个更高级的控件。
这种方式,对外暴露的接口比较少,减少了上层使用成本,但也因此增强了控件的复用性。在 Flutter 中,组合的思想始终贯穿在框架设计之中,这也是 Flutter 提供了如此丰富的控件库的原因之一。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Flutter核心技术与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(23)

  • 和小胖
    关于第二道思考题目的解决方法,请老师看一下:

    //绘制自定义view,其中画笔 paint ,画布 canvas,而 CustomPainter 负责具体的绘制逻辑处理
    class WheelPainter extends CustomPainter {
      List<double> _list;
      List<Color> _listColor;
      double _total; //总份数

      WheelPainter(this._list, this._listColor);

      @override
      void paint(Canvas canvas, Size size) {
        double wheelSize = min(size.width, size.height) / 2; //饼图的尺寸
        //用一个矩形框来包裹饼图
        Rect boundingRect = Rect.fromCircle(
            center: Offset(wheelSize, wheelSize), radius: wheelSize);
        //求出数组中所有数值的和
        _total = _list.reduce((value, element) => value + element);
        print("总份额是:$_total");
        //求出每一份所占的角度
        double radius = (2 * pi) / _total; //求出每一份的弧度
        print("总角度是${2 * pi},每份额角度是:$radius");
        //循环绘制每一份扇形
        for (int i = 0; i < _list.length; i++) {
          // 求出初始角度
          double startRadius;
          if (i == 0) {
            startRadius = 0;
          } else {
            startRadius =
                _list.sublist(0, i).reduce((value, element) => value + element) *
                    radius;
          }
          //求出滑过角度,即当前份额乘以每份所占角度
          double sweepRadius = radius * _list[i];
          print("起始角度:$startRadius,划过角度:$sweepRadius");

          /// 绘制扇形,第一个参数是绘制矩形所在的矩形,第二个参数是起始角度,第三个参数是矩形划过的角度
          /// 第三个参数代表扇形是否是实心,否则就只是绘制边框是空心的,最后一个参数是画笔
          canvas.drawArc(boundingRect, startRadius, sweepRadius, true,
              getPaintByColor(_listColor[i]));
        }
      }

      //根据不同的 color 来获取对应的画笔
      Paint getPaintByColor(Color color) {
        Paint paint = Paint();
        paint.color = color;
        return paint;
      }

      //判断是否需要重绘
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
    }

    Padding getCustomPaint() {
      return Padding(
          padding: EdgeInsets.all(10),
          child: CustomPaint(
            size: Size(200, 200),
            painter: WheelPainter(
                List.of([1.0, 2.0, 3.0, 4.0, 20]),
                List.of([
                  Colors.red,
                  Colors.blue,
                  Colors.green,
                  Colors.amber,
                  Colors.black54
                ])),
          ));
    }

    作者回复: 很厉害👍!

    2019-09-05
    2
    3
  • 獸丶
    老师,Flutter代码规范方面会讲吗,如果代码全写在一个Dart文件里,有点太冗杂了,还是说遵循SRP,一个类一个Dart文件?

    作者回复: 后面会捎带讲一点点

    2019-08-02
    2
  • davidzhou
    我的思路这样,先自定义一个statefulwidget,里面用过一个变量控制两个text,因为text是statelesswidget,无法动态去刷新,一个widget设置Maxlines=2,另一个不设置,more是一个floatbutton,点击事件里面实现setstate改变先前定义的变量就行了

    作者回复: 赞

    2019-08-01
    2
  • 大土豆
    一看到Canvas,就知道又把rn和weex给秒杀了,这两家伙没有画布。。。没法绘制
    2019-08-01
    1
    2
  • 吴小安
    大前端的界面不是提倡尽量减少图层的数量?这样一直嵌套下去图层似乎太多,这些布局的控件是不是不算图层?不参与渲染?

    作者回复: 1.大部分都是非可视的容器,并不参与绘制;对于非透明的视图叠加,Flutter在绘制完毕后会做图层合并的

    2019-08-01
    2
  • wanggw
    功能算是差不多实现了,但是还存在一个核心的问题不知道应该解决,就是我怎么获取Text展示文本的行数,我需要行数才能控制 more 按钮的显示和隐藏,否则我默认超过2行显示 more 按钮,当文本只有1行的时候,more 按钮也显示了😂。暂时还没找到解决方案。这是我写的例子:https://github.com/wanggw911/flutter_hello/blob/master/lib/widget/Listview02.dart

    作者回复: 如果想简单点,可以预估一个文本长度(比如100),超过这个文本长度的两行文字,统一都展示“More”按钮;如果想精确点,算文本高度可以用TextPainter。具体用法可以参考auto_size_text控件:https://github.com/leisim/auto_size_text

    2019-08-22
    2
    1
  • Neil 陈荣
    老师,我想绘制一个围棋游戏的棋盘,并在上面实现落子、提子等各种操作。大致估计了一下,如果用组合的方案,算上棋子,棋盘,各种线,至少会有接近400个 widget. 这种情况下性能会有问题吗?我想知道 widget 数量一般在多少以内采用组合不会出现性能问题,这个有没有一个指导性的最佳实践?如果一上来就用自绘的方案的话,担心各种操作的交互功能不容易实现。谢谢!
    2019-12-11
  • Captain
    老师,pi是在哪里定义的?

    作者回复: math.dart

    2019-11-18
  • 毛哥来了
    我是用Builder(builder: (Buildcontext context){})来创建description那个Text组件,然后就可以通过context.size.height获取Text的高度,然后就能判断按钮何时展示了

    作者回复: 赞

    2019-10-24
  • leslee
    老师我把你的案例跑起来了, 但是我把 我把计数器那个案列的代码里面的 MaterialApp 的 themeData 的 primaryColor 的颜色改成你的 ligjtblue[800] 后他报错, 报 type color is not a subtype of type materialcolor 我看了文档确实颜色是分好几个类型, 可是为什么你的可以运行我的不可以运行........

    作者回复: 看一下是不是头文件没引

    2019-10-21
    1
  • 离尘不离人คิดถึง
    功能都实现了,抽象了一个有状态的组件:https://github.com/lichenbuliren/flutter_study/blob/master/lib/components/appUpdateItemCard.dart;
    基本思路:
    1、用 Contaner 的 decoration 绘制一个圆角白色渐变的白底;
    2、抽取 UI 组件,内部维护一个 『showMore』 状态
    3、描述文本组件根据 『showMore』 来动态设置 『maxLines』属性值,并且设置 『overflow』为省略号
    唯一存在的小瑕疵就是我的 FlatButton 控件中的文本一直存在 padding;手动设置了 0 也不生效;因为我看该章节里面的 『more』文本应该是靠右对齐的,这里麻烦老师帮忙看下,我尝试了各种方案都没法实现

    作者回复: 你参考36节的布局调试分享,直接在Flutter Inspector里改布局参数

    2019-10-20
  • zzz
    请问下,再混合开发的场景下,module类型的对于root Widget,我们必须使用MaterialApp或者CupertinoApp二选一么?如果不使用这两个基础的widgetApp,除了不能享受封装好的Theme、封装好的Widget组件这些便利外,是否有无法实现的基础功能(比如在iOS中的右滑返回等等)? 同时想问下目前国内很多App在安卓上的表现都不是Material风格的,同时也不完全是Cupertino风格,所以在实际应用中,主流的做法是使用 MaterialApp/CupertinoApp/完全自定义 这三种的哪一种呢?以及在开发过程中的是否有什么弊端呢?

    作者回复: MaterialApp和CupertinoApp封装了一些基本的App功能,比如导航栈管理、屏幕管理,国际化,以及对应的脚手架等,自己实现没有必要也基本不可能。样式就是一个配置而已,你可以自己定义不同平台的配置规则(具体参考夜间模式这一节)。另外他俩对Android/iOS的关键设计差异(如导航栏样式、状态栏样式、弹窗样式等),在框架内就进行了区分实现,所以如果你不定制样式,也无需过多担心在iOS上长得太像Android。

    2019-10-08
  • 🌝
    我把描述信息这块提成了一个有状态的widget,这样性能应该会更好些

    class DescriptionModel extends StatefulWidget {
      String desc;

      DescriptionModel({Key key, this.desc}) : super(key: key);

      _DescriptionModelState createState() => _DescriptionModelState();
    }

    class _DescriptionModelState extends State<DescriptionModel> {

      bool flag = false;

      void changeFlag (){
        print(flag);
        setState(() {
          flag = !flag;
        });
      }

      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Text(widget.desc, maxLines: flag ? null : 2,),
            Positioned(
              bottom: 0,
              right: -5,
              child: FlatButton(
                // color: Colors.white,
                highlightColor: Colors.transparent,
                splashColor: Colors.transparent,
                onPressed: changeFlag,
                child: Container(child: Text('More', style: TextStyle(color: Colors.blue),), padding: EdgeInsets.fromLTRB(5,0,5,0), color: Colors.white,)
              ),
            )
          ],
        );
      }
    }

    功能是实现了,但是这个按钮摆放的位置让我很头疼,而且 按钮的样式也不知道该怎么去改变,我想去改变按钮的宽高以便让按钮显示的更正常些。

    作者回复: 可以试试shape、width、height这几个属性

    2019-09-20
  • 🌝
    我把描述信息这一块提成了一个有状态的widget
    2019-09-20
  • 和小胖
    老师,没太明白第二道题的意思,传入的数组的值是弧度吗?那这样的话,传入的数组的所有元素加起来得够 2π 是吗?

    作者回复: 传入的数组是数值类型,数组有几个元素饼图就画几块,而饼图每一块的面积占比对应着该元素的数值大小

    2019-09-04
  • 浣熊特工队
    请问老师,more按钮那里的左右渐隐是怎么实现的啊,我用TextOverflow.fade是向下渐隐的啊

    作者回复: 可以看下这个issue:https://github.com/flutter/flutter/issues/13631
    另外这种效果可以考虑用Button遮挡来实现

    2019-09-03
    1
  • 🌙
    如果说尽量使用stateless,但是只要需要交互都必须是stateful吧?怎么尽量来使用成stateless呢?

    作者回复: 把组件拆小

    2019-08-23
  • 楼外楼
    哪些算是非可视容器呢?

    作者回复: 不参与绘制,仅参与布局,或是仅提供数据的组件

    2019-08-10
  • 灰灰
    class UpdateItemWidget extends StatefulWidget {
      final model;
      final onPressed;

      UpdateItemWidget({Key key, this.model, this.onPressed}) : super(key: key);
      @override
      State<StatefulWidget> createState() {
        return _UpdateItemState();
      }
    }
    class _UpdateItemState extends State<UpdateItemWidget> {
      bool collapse = true;
      @override
      void initState() {
        super.initState();
      }
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            buildTopRow(context),
            buildBottomRow(context),
          ],
        );
      }
     ....
      Widget buildBottomRow(BuildContext context) {
        return Padding(
          padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                    child: Text(
                      widget.model.appDescription,
                      maxLines: collapse ? 2 : null,
                      overflow: collapse ? TextOverflow.ellipsis : null),
                  ),
                  Container(
                    width: !collapse ? 0 : null,
                    child: FlatButton(
                      onPressed: () {
                        setState(() {collapse = false;});
                        },
                      child: Text('More', style: TextStyle(color: Colors.blue),),
                    )
                  ),
                ],
              ),
              Padding(
                padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
                child: Text("${widget.model.appVersion} • ${widget.model.appSize} MB"),
              )
            ],
          ),
        );
      }
    }

    作者回复: 功能没问题,不过你的按钮位置把Text位置挤压了

    2019-08-05
    1
  • 许童童
    老师,用Positioned组件设置
    right: 0,
    bottom: 0,
    FlatButton组件总对齐不了最下边。

    作者回复: 你把FlatButton换成RaisedButton就明白了

    2019-08-01
收起评论
23
返回
顶部