flutter 布局与绘制(下)

上篇我们以Container,Center为例通过源码分析找到了为什么Container设置的宽高会不生效的原因,本篇将着重介绍Center是如何将child绘制到中间的,以此为例来探究flutter父widget对子widget的布局,以及子widget如何绘制的?
下面我们看看官网举的一个例子:如果一个 widget 中包含了一个具有 padding 的 Column,并且要对 Column 的子 widget 进行如下的布局:


image.png

那么谈判将会像这样:
Widget: “嘿!我的父级。我的约束是多少?”
Parent: “你的宽度必须在 80 到 300 像素之间,高度必须在 30 到 85 之间。”
Widget: “嗯…我想要 5 个像素的内边距,这样我的子级能最多拥有 290 个像素宽度和 75 个像素高度。”
Widget: “嘿,我的第一个子级,你的宽度必须要在 0 到 290,长度在 0 到 75 之间。”
First child: “OK,那我想要 290 像素的宽度,20 个像素的长度。”
Widget: “嗯…由于我想要将我的第二个子级放在第一个子级下面,所以我们仅剩 55 个像素的高度给第二个子级了。”
Widget: “嘿,我的第二个子级,你的宽度必须要在 0 到 290,长度在 0 到 55 之间。”
Second child: “OK,那我想要 140 像素的宽度,30 个像素的长度。”
Widget: “很好。我的第一个子级将被放在 x: 5 & y: 5 的位置,而我的第二个子级将在 x: 80 & y: 25 的位置。”
Widget: “嘿,我的父级,我决定我的大小为 300 像素宽度,60 像素高度。”

根据以上我们能得出如下的结论:

  • 一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。这意味着 widget 通常情况下 不能任意获得其想要的大小。
  • 一个 widget 无法知道,也不需要决定其在屏幕中的位置。因为它的位置是由其父级决定的。
  • 当轮到父级决定其大小和位置的时候,同样的也取决于它父级自身的父级。所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。
  • 如果子级想要拥有和父级不同的大小,然而父级没有足够的空间对其进行布局的话,子级的设置的大小可能会不生效。 这时请明确指定它的对齐方式
    上一节我们找到了Center对应的render RenderPositionedBox,在它的performLayout里我们找到了约束的传递。要想将widget正确的绘制出来,不仅要有约束,还需要具体的位置信息。说到绘制我们第一想到的就是看看RenderPositionedBox的paint方法都做了啥?
 @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
      context.paintChild(child!, childParentData.offset + offset);
    }

咦!这么简单,看样子就是传了个偏移量,也就是确定child的坐标。但是这个里面的childParentData是从哪来的呢?
前面的分析我们知道约束是在performLayout()方法里传递过来的,那么childParentData会不会也是在这个方法里呢,毕竟跟布局是相关的。看下源码:

@override
  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;

    if (child != null) {
      child!.layout(constraints.loosen(), parentUsesSize: true);
      size = constraints.constrain(Size(shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
                                        shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity));
      alignChild(); // align 是不是对位置的确认呢?
    } else {
      size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
                                        shrinkWrapHeight ? 0.0 : double.infinity));
    }
  }

这里我们看到alignChild()这个方法,它是不是对位置的确认呢?看下源码不就知道了吗

 @protected
  void alignChild() {
    _resolve(); // 重新赋值_resolvedAlignment 
    assert(child != null);
    assert(!child!.debugNeedsLayout);
    assert(child!.hasSize);
    assert(hasSize);
    assert(_resolvedAlignment != null);
/// 在此处对childParentData做了相应的处理
    final BoxParentData childParentData = child!.parentData! as BoxParentData;
    childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
  }
///alongOffset  ,由于是Aligment.center,所以x,y都为0
Offset alongOffset(Offset other) {
    final double centerX = other.dx / 2.0;
    final double centerY = other.dy / 2.0;
    return Offset(centerX + x * centerX, centerY + y * centerY);
  }

以上方法就得出了Container对应的render->RenderConstraintBox的parentData了。有了约束,确定了大小,有了位置,那么是不是就可以绘制上去了。我们继续看下Container对应的render,因为我们给Container设置了颜色,并且给了它Center的父widget,所以Container会对应两个RenderObjectWidget,一个是ColoredBox,一个是ConstrainedBox,并且ColoredBox是作为ConstrainedBox的child的。
先看下ConstrainedBox对应的renderView的paint 方法,没有做处理,直接去执行child的paint方法,也就是ColoredBox对应的RenderObject-->_RenderColoredBox 的paint方法,方法如下:

@override
  void paint(PaintingContext context, Offset offset) {
    // It's tempting to want to optimize out this `drawRect()` call if the
    // color is transparent (alpha==0), but doing so would be incorrect. See
    // https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for
    // a good description of why.
    if (size > Size.zero) {
      context.canvas.drawRect(offset & size, Paint()..color = color);
    }
    if (child != null) {
      context.paintChild(child!, offset);
    }
  }

至此一个100*100的红色Container就被绘制出来了。我们注意到child!.layout(constraints.loosen(), parentUsesSize: true)这个方法,发现当parentUsesSize为true时,父组件会使用child的size,在child的size发生变化时,会重新布局。

image.png

总结:


image.png

demo请参考:https://github.com/osCoding/flutter_learn

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容