Flutter 102: 何为 Flutter RenderObjects ?

简介: 0 基础学习 Flutter,第一百零二步:简单学习 RenderObject 的基本知识!

      小菜前段时间简单了解了一下 WidgetElement,其中 Widget 主要是存放渲染内容以及布局信息等,仅作为一个信息存储的容器;Element 主要用于存放上下文环境,遍历 UI View 视图树;而小菜今天尝试学习的 RenderObject 才是 UI View 真正的渲染部分;

RenderObject

      RenderObject 作为渲染树中的一个对象;其 layout()paint() 是渲染库核心,负责管理布局和渲染等;RenderObject 定义了布局绘制协议,但并没定义具体布局绘制模型;

源码分析

      RenderObject 可以从多个维度研究,可以通过 layout()paint() 对比 Android 的绘制流程,也可以根据其属性和交互的对象(parent / owner / child)来学习;小菜从头开始为了尽可能多的了解源码,尝试第二种方式进一步学习;

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
    AbstractNode _rootNode;
    ParentData parentData;
    Constraints _constraints;
    @protected
    Constraints get constraints => _constraints;
    PipelineOwner _owner;
    bool get attached => _owner != null;
    void setupParentData(covariant RenderObject child) {}
    void adoptChild(RenderObject child) {}
    void dropChild(RenderObject child) {}
    void attach(PipelineOwner owner) {}
    void detach() {}
}

parent 相关

1. ParentData
ParentData parentData;

void setupParentData(covariant RenderObject child) {
    assert(_debugCanPerformMutations);
    if (child.parentData is! ParentData)
      child.parentData = ParentData();
}

      RenderObject 包括两个重要属性 parentParentData 插槽;ParentData 做为一个预留的变量,由 parent 赋值,传递信息给 child 的存储容器;通常所有和 child 特定的数据都可以存储在 ParentData 中;

2. Constraints
void layout(Constraints constraints, { bool parentUsesSize = false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
   
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) return;
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      visitChildren((RenderObject child) {
        child._cleanRelayoutBoundary();
      });
    }
    _relayoutBoundary = relayoutBoundary;
    
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
}

      Constraints 作为 RenderObjectparentchild 之间的布局约束;layout() 作为 RenderObject 的核心方法,需要传入 Constraints 作为约束,配合 parentUsesSize 判断 RenderObjectchild 子节点发生变化时,parent 父节点是否需要重新绘制;

3. relayoutBoundary
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
  relayoutBoundary = this;
} else {
  final RenderObject parent = this.parent;
  relayoutBoundary = parent._relayoutBoundary;
}
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
  visitChildren((RenderObject child) {
    child._cleanRelayoutBoundary();
  });
}

void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
}

      layout() 中定义了一个 RenderObject 类型的 relayoutBoundary 布局边界,如果布局边界发生变化,则遍历清空所有已记录的边界并重新设置;

      markNeedsLayout() 中也需要进行布局边界判断,若 RenderObject 自身不是 relayoutBoundary,则向 parent 父节点查找,直到找到确定是 relayoutBoundaryRenderObject 并标记为 dirty

      layout() 确定自己是否为边界需要判断四个条件,分别是 !parentUsesSize parent 父节点是否关心自己的大小;sizedByParent 是否由 parent 父节点判断大小;constraints.isTight 是否严格约束;parent is! RenderObject 自身是否为 root 根节点;

owner 相关

      PipelineOwner 作为整个渲染流程的管理者;提供用于驱动渲染管道的接口,并存储在管道的每个阶段中已请求访问渲染对象的状态等;

1. flushLayout
void flushLayout() {
    if (!kReleaseMode) {
      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
    }
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      if (!kReleaseMode)  Timeline.finishSync();
    }
}

      flushLayout() 用于遍历所有标记为 dirty 的需要重新布局的 RenderObjects 并重新计算其布局尺寸和位置等;

2. flushCompositingBits
void flushCompositingBits() {
    if (!kReleaseMode) {
      Timeline.startSync('Compositing bits');
    }
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    if (!kReleaseMode) {
      Timeline.finishSync();
    }
}

      flushCompositingBits() 用于遍历所有标记为 dirty 的需要 CompositingBitsUpdate 合并更新的子节点,再次阶段,每个 RenderObject 都会了解其子节点是否需要合并更新;

3. flushPaint
void flushPaint() {
    if (!kReleaseMode) {
      Timeline.startSync('Paint', arguments: timelineWhitelistArguments);
    }
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            PaintingContext.repaintCompositedChild(node);
          } else { node._skippedPaintingOnLayer(); }
        }
      }
    } finally {
      if (!kReleaseMode) { Timeline.finishSync(); }
    }
}

      flushPaint() 用于遍历所有标记为 dirty 的需要重新绘制的子节点,并生成 Layer 用于绘制展示;

4. attach / detach
@override
void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_needsLayout && _relayoutBoundary != null) {
      _needsLayout = false;
      markNeedsLayout();
    }
    if (_needsCompositingBitsUpdate) {
      _needsCompositingBitsUpdate = false;
      markNeedsCompositingBitsUpdate();
    }
    if (_needsPaint && _layer != null) {
      _needsPaint = false;
      markNeedsPaint();
    }
    if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
      _needsSemanticsUpdate = false;
      markNeedsSemanticsUpdate();
    }
}

      layout() 中在 attach()detach() 中也需要 PipelineOwnerattach() 主要通知管理者 owner 将其插入到渲染树中标记需要计算布局 layout 并调用 markNeedsPaint 重新绘制;detach() 主要是通知管理者取消关联;

child 相关

      对于 child 子节点,小菜主要学习如下三个方法;

1. adoptChild
@override
void adoptChild(RenderObject child) {
    setupParentData(child);
    markNeedsLayout();
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
    super.adoptChild(child);
}

      adoptChild() 主要是 RenderObject 添加一个 child 子节点;其中需要通过 setupParentData() 来获取 ParentData 中的数据并更新;

2. dropChild
@override
void dropChild(RenderObject child) {
    child._cleanRelayoutBoundary();
    child.parentData.detach();
    child.parentData = null;
    super.dropChild(child);
    markNeedsLayout();
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
}

      dropChild() 是和 adoptChild() 对应的方法,主要用于 RenderObject 删除一个 child 子节点;删除过程中需要 _cleanRelayoutBoundary 清除边界并删除 ParentData,之后再更新;

3. paintChild
void paintChild(RenderObject child, Offset offset) {
    if (child.isRepaintBoundary) {
      stopRecordingIfNeeded();
      _compositeChild(child, offset);
    } else {
      child._paintWithContext(this, offset);
    }
}

      paintChild() 为绘制一个子节点的 RenderObject;如果该子节点有自己合成层,则 child 子节点将被合成到与此绘制相关的上下文相关的 Layer 层中;

RenderBox

      RenderObject 并没定义具体布局绘制模型,所以小菜简单学习了一下 RenderBoxRenderBoxRenderObject 的子类,以屏幕左上角为原点(包括顶部状态栏)坐标系;BoxParentData 作为 child 子节点传输数据,BoxConstraints 作为其约束条件,通过 Size 记录其尺寸大小;可以定义具体的布局绘制模型;


      RenderObject 涉及的方式方法较多,小菜对于源码的理解还不够深入,如有错误,请多多指导!

来源: 阿策小和尚

目录
相关文章
|
4天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
4天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
2天前
|
前端开发 C++ 容器
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
|
2天前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
2天前
|
XML Dart Java
Flutter插件开发之APK自动安装,字节跳动Android岗面试题
Flutter插件开发之APK自动安装,字节跳动Android岗面试题
|
3天前
|
Java Android开发 设计模式
flutter音视频开发,Android开发需要学什么
flutter音视频开发,Android开发需要学什么
|
4天前
|
Dart 前端开发 测试技术
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
【4月更文挑战第30天】随着Flutter在跨平台开发的普及,保证代码质量成为开发者关注的重点。优质代码能确保应用性能与稳定性,提高开发效率。关键策略包括遵循最佳实践,编写可读性强的代码,实施代码审查和自动化测试。重构实践在项目扩展时尤为重要,适时重构能优化结构,降低维护成本。开发者应重视代码质量和重构,以促进项目成功。
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
|
4天前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
4天前
|
Dart 前端开发 安全
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
【4月更文挑战第30天】本文探讨了Flutter中线程管理和并发编程的关键性,强调其对应用性能和用户体验的影响。Dart语言提供了`async`、`await`、`Stream`和`Future`等原生异步支持。Flutter采用事件驱动的单线程模型,通过`Isolate`实现线程隔离。实践中,可利用`async/await`、`StreamBuilder`和`Isolate`处理异步任务,同时注意线程安全和性能调优。参考文献包括Dart异步编程、Flutter线程模型和DevTools文档。
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
|
4天前
|
Dart 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的性能分析工具Profiler
【4月更文挑战第30天】Flutter Profiler是用于性能优化的关键工具,提供CPU、GPU、内存和网络分析。它帮助开发者识别性能瓶颈,如CPU过度使用、渲染延迟、内存泄漏和网络效率低。通过实时监控和分析,开发者能优化代码、减少内存占用、改善渲染速度和网络请求,从而提升应用性能和用户体验。定期使用并结合实际场景与其它工具进行综合分析,是实现最佳实践的关键。
【Flutter前端技术开发专栏】Flutter中的性能分析工具Profiler