本文基于1.12.13+hotfix.8版本源码分析。

0、大纲

  1. RenderBox的用法
  2. 通过RenderObjectWidget把RenderBox塞进界面

1、RenderBox

在flutter中,我们最常接触的,莫过于各种各样的widget了,但是,实际负责渲染的RenderObject是很少接触的(它们之间的关联可以看看闲鱼的这篇文章:https://www.yuque.com/xytech/flutter/tge705)。而作为一名天天向上的程序员,我们自然要去学习一下它的原理,做到知其然且知其所以然。本文会先来看看RenderBox的用法,以此抛砖引玉,便于后面继续深入flutter的绘制原理。

首先,RenderBox是RenderObject的子类,它将坐标系转换成我们熟知的笛卡尔坐标系,更便于使用。使用RenderBox进行绘制(本文暂时不讲child),我们需要做三件事:

(1)测量

第一步,我们需要确定视图大小,并赋值给父类的size属性。测量有两种情况,第一种是size由自身决定,第二种是由parent决定。

首先,由自身决定size的情况,需要在performLayout方法中完成测量,通过父类的constraints可得到满足约束的值:

  @override
  void performLayout() {
    size = Size(
      constraints.constrainWidth(200),
      constraints.constrainHeight(200),
    );
  }

第二种情况,size由parent决定,这种情况下视图大小应该完全通过parent提供的constraints测量,不存在其它因素。这种情况下,只要parent的约束不发生变化,就不会重新测量。

这种情况需要重写sizedByParent并返回true,然后在performResize中完成测量,并且performLayout中不能对size赋值! 感兴趣的同学也可以两边都写,看看会发生什么:)。

  @override
  void performLayout() {}

  @override
  void performResize() {
    size = Size(
      constraints.constrainWidth(200),
      constraints.constrainHeight(200),
    );
  }

  @override
  bool get sizedByParent => true;
(2)绘制

RenderBox的绘制与android原生的view绘制非常相似,同样是Paint+Canvas的组合,而且api也非常接近,会非常容易上手。

  @override
  void paint(PaintingContext context, Offset offset) {
    Paint paint = Paint()
      ..color = _color
      ..style = PaintingStyle.fill;
    context.canvas.drawRect(
        Rect.fromLTRB(
          0,
          0,
          size.width,
          size.height,
        ),
        paint);
  }

这样是不是就万事大吉了呢?如果通过上面的代码进行绘制,你会发现,不管在外层怎么设置位置,绘制出来的矩形都是固定在屏幕左上角的!怎么回事?

这里就是flutter中绘制与android的最大不同:在这里绘制的坐标系是全局坐标系,即原点在屏幕左上角,而非视图左上角。

细心的同学可能已经发现,paint方法中还有一个offset参数,这就是经过parent的约束后,当前视图的偏移量,绘制时应该将它考虑进去:

  @override
  void paint(PaintingContext context, Offset offset) {
    Paint paint = Paint()
      ..color = _color
      ..style = PaintingStyle.fill;
    context.canvas.drawRect(
        Rect.fromLTRB(
          offset.dx,
          offset.dy,
          offset.dx + size.width,
          offset.dy + size.height,
        ),
        paint);
  }
(3)更新

在flutter中,是由Widget的配置发生变更而引起的rebuild,而这就是我们要实现的第三步:当视图属性发生变更时,标记重新布局或重新绘制,当屏幕刷新时就会做相应的刷新。

这里涉及到两个方法:markNeedsLayout、markNeedsPaint。顾名思义,前者标记重布局,后者标记重绘。

我们需要做的,就是根据属性的影响范围,在更新属性时,调用合适的标记方法,例如color变化时调用markNeedsPaint,width变化时调用markNeedsLayout。另外,两者都需要更新的情况下,调用markNeedsLayout即可,不需要两个方法都调。

  set width(double width) {
    if (width != _width) {
      _width = width;
      markNeedsLayout();
    }
  }

  set color(Color color) {
    if (color != _color) {
      _color = color;
      markNeedsPaint();
    }
  }

2、RenderObjectWidget

(1)简介

上面讲了一大堆RenderBox的用法,但是,这玩意儿怎么用到我们熟知的Widget里面去?

按照正常流程,我们得实现一个Element和一个Widget,然后在Widget中创建Element,在Element中创建和更新RenderObject,另外还得管理一大堆状态,处理非常繁琐。所幸flutter为我们封装了这一套逻辑,即RenderObjectWidget。

相信对flutter有所了解的同学都对StatelessWidget和StatefulWidget不陌生,但其实,StatelessWidget和StatefulWidget仅负责属性、生命周期等的管理,在它们的build方法实现中都会创建RenderObjectWidget,通过它来实现与RenderObject的关联。

举个栗子,我们经常使用的Image是个StatefulWidget,对应的state的build方法中实际返回了一个RawImage对象,而这个RawImage是继承自LeafRenderObjectWidget的,这正是RenderObjectWidget的一个子类;再比如Text,它build方法中创建的RichText是继承自MultiChildRenderObjectWidget,这同样是RenderObjectWidget的一个子类。

我们再看看RenderObjectWidget顶部的注释即可明白:

RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
which wrap [RenderObject]s, which provide the actual rendering of the
application.

大概意思就是RenderObject才是实际负责渲染应用的,而RenderObjectWidget提供包装了RenderObject的配置,方便我们使用。

另外,flutter还分别实现了几个子类,进一步封装了RenderObjectWidget,它们分别是LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。其中,LeafRenderObjectWidget是叶节点,不含子Widget;SingleChildRenderObjectWidget仅有一个child;而MultiChildRenderObjectWidget则是含有children列表。这几个子类根据child的情况分别创建了对应的Element,所以通过这几个子类,我们只需要关注RenderObject的创建和更新。

(2)用法

以最简单的LeafRenderObjectWidget为例,我们需要实现createRenderObject、updateRenderObject两个方法:

  class CustomRenderWidget extends LeafRenderObjectWidget {
  CustomRenderWidget({
    this.width = 0,
    this.height = 0,
    this.color,
  });

  final double width;
  final double height;
  final Color color;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomRenderBox(width, height, color);
  }

  @override
  void updateRenderObject(BuildContext context, RenderObject renderObject) {
    CustomRenderBox renderBox = renderObject as CustomRenderBox;
    renderBox
      ..width = width
      ..height = height
      ..color = color;
  }
}

Flutter RenderBox指南——绘制篇的更多相关文章

  1. 【转载】 Spark性能优化指南——基础篇

    转自:http://tech.meituan.com/spark-tuning-basic.html?from=timeline 前言 开发调优 调优概述 原则一:避免创建重复的RDD 原则二:尽可能 ...

  2. 【转】【技术博客】Spark性能优化指南——高级篇

    http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651745207&idx=1&sn=3d70d59cede236e ...

  3. 【转】Spark性能优化指南——基础篇

    http://mp.weixin.qq.com/s?__biz=MjM5NDMwNjMzNA==&mid=2651805828&idx=1&sn=2f413828d1fdc6a ...

  4. 【Todo】【读书笔记】Java多线程编程指南-设计模式篇

    下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看.

  5. Spark性能优化指南——基础篇(转载)

    前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作 ...

  6. Spark性能优化指南——基础篇

    本文转自:http://tech.meituan.com/spark-tuning-basic.html 感谢原作者 前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一 ...

  7. Spark性能优化指南——高级篇

    本文转载自:https://tech.meituan.com/spark-tuning-pro.html 美团技术点评团队) Spark性能优化指南——高级篇 李雪蕤 ·2016-05-12 14:4 ...

  8. Java工程师学习指南 完结篇

    Java工程师学习指南 完结篇 先声明一点,文章里面不会详细到每一步怎么操作,只会提供大致的思路和方向,给大家以启发,如果真的要一步一步指导操作的话,那至少需要一本书的厚度啦. 因为笔者还只是一名在校 ...

  9. Java工程师学习指南 中级篇

    Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...

随机推荐

  1. redis: Set集合类型(五)

    Set里面的值是不能重复的 Set设置值(头部):sadd myset hello Set获取值:smembers myset 检查Set是否包含某个元素:sismember myset hello ...

  2. 新增记录txt文档

    StringBuilder s = new StringBuilder(); s.Append("[" + dt.Rows[i]["Store"].Conver ...

  3. UnicodeDecodeError: 'gbk' codec can't decode byte 0x8a in position 2: illegal multibyte sequence

    pycharm报错UnicodeDecodeError: 'gbk' codec can't decode byte 0x8a in position 2: illegal multibyte seq ...

  4. SweetAlert - 演示6种不同的提示框效果

    http://www.sucaihuo.com/js/190.html http://www.cnblogs.com/beiz/p/5238124.html

  5. MySQL不同存储引擎下optimize的用法

    optimize命令是mysql的常用优化命令,但是在InnoDB与MyISAM这两个存储引擎中却有很大的分别.本文将对这两个常用的存储引擎进行区分跟实例解析 1.查看mysql当前的存储引擎 一般情 ...

  6. C语言实现数组循环左移

    c语言实现数组左移: 例如输入: 8 3 1 2 3 4 5 6 7 8 输出: 4 5 6 7 8 1 2 3 #include <stdio.h> int main(int argc, ...

  7. SpringBoot 集成Swagger2自动生成文档和导出成静态文件

    目录 1. 简介 2. 集成Swagger2 2.1 导入Swagger库 2.2 配置Swagger基本信息 2.3 使用Swagger注解 2.4 文档效果图 3. 常用注解介绍 4. Swagg ...

  8. Linux指令面试题01-进程查看与终止

    查看某一进程是否运行:ps -ef|grep 程序名 终止程序: kill pid 转载于:https://www.cnblogs.com/feihujiushiwo/p/10896636.html

  9. CSS开发技巧(二):表格合并边框后的单元格宽度计算

    前言: 分离边框模型和合并边框模型是表格的两种模型,它通过以下属性确定: border-collapse:separate(默认值) | collapse | inherit 当采用分离边框模型时,表 ...

  10. 4.shell基本操作简介

    判断一个命令是不是内置命令,可以用type命令 1.printf :冒号 #:〉 test.txt 这里会建立一个空文件test.txt set -o|grep  emacs 查看 emacs 模式是 ...