如何理解Flutter中的RenderObject ?详细解析

2021年11月30日02:42:37 发表评论 211 次浏览
如何理解Flutter中的RenderObject ?详细解析
Flutter RenderObject用法示例

介绍

你可能曾听说过“Flutter 中的一切都是小部件”这句话,在某个时候。而且,事实上,在顶层显示的所有内容——你在屏幕上、UI 中看到的所有内容——都是一个小部件。

然而,你有没有想过幕后发生了什么?如何小部件形成各种形状,文字和图片,我们在屏幕上看到了什么?如何理解Flutter中的RenderObject

这些问题至关重要,它们的答案将大大帮助你成为更好的 Flutter 开发人员。正确理解 Flutter 小部件转换为我们看到并与之交互的 UI 屏幕的步骤将进一步帮助我们正确使用可用资源来满足特殊需求,例如创建自定义布局、超级自定义绘画等。

本文旨在带你逐步了解 Flutter 表面(小部件)下的奥秘。

Flutter 中的渲染是如何工作的?

Flutter如何使用RenderObject?在我们开始介绍 RenderObjects 及其用途、功能和重要性之前,让我们快速了解一下 Flutter 中的渲染是如何发生的。

Flutter 使用在传递给小部件的字段或参数中保存配置信息的小部件。这里的小部件充当各种“容器”:它保存这些配置参数但不使用它们。小部件实例化并膨胀为一个元素。

这个元素被插入到元素树中并代表小部件,元素树中的每个元素都有一个附加的RenderObject。这些 RenderObjects 负责控制这些配置参数,如尺寸、布局和小部件在屏幕上的绘制,形成我们看到的 UI。

如何理解Flutter中的RenderObject ?详细解析
资料来源: 学术界

查看从小部件到元素再到 RenderObject 的流程,你可能会注意到主要工作发生在 RenderObject 中——诸如调整大小、将小部件绘制到屏幕以及操纵各种参数等都发生在 RenderObject 内部。

了解 RenderObjects 肯定会帮助你构建高质量的移动应用程序。那么这些RenderObject到底是什么呢?

什么是RenderObjects?

如何理解Flutter中的RenderObject?RenderObjects 是那些负责控制大小、布局和逻辑的特定“对象”,用于将小部件绘制到屏幕并形成应用程序的 UI。你可以说实际渲染发生在RenderObjects 中

但它们很少使用,因为在十分之九的情况下,开发人员不需要使用它们。小部件足以满足大多数开发人员的需求。

但是,在某些特定情况下,超级复杂的设计需要精确实现。使用小部件或通过使用更多香料从头开始构建特定小部件或使其更易于使用可能不是完全可能的。在这样的情况下,RenderObjects 将是正确的使用工具。

了解运行中的 RenderObjects:作为案例研究的不透明度小部件

让我们看一下Opacity 小部件,以更好地了解从小部件到元素再到 RenderObject 的链接。不透明度小部件调整其子项的透明度。

关于小部件将扩展的 RenderObject 类型需要注意的一些关键事项:

由于我们正在研究的 Opacity 小部件接受它正在调整其透明度的子级,因此它必须扩展 SingleChildRenderObjectWidget。

反过来,SingleChildRenderObjectWidget 扩展了 RenderObjectWidget。最后,RenderObjectWidget 扩展了 Widget 类。

//Opacity extends SingleChildRenderObjectWidget
class Opacity extends SingleChildRenderObjectWidget {

// SingleChildRenderObjectWidget extends RenderObjectWidget
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {

// RenderObjectWidget extends Widget
abstract class RenderObjectWidget extends Widget {

那么,我们为什么要看谁扩展了什么?SingleChildRenderObjectWidget 类有一个方法负责创建元素。回想一下,特定小部件的元素是它的实例,并指向它在元素树上的位置。它附加到小部件。该元素是 SingleChildRenderObjectElement 并且是树上的 Opacity 小部件的实例。

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
 const SingleChildRenderObjectWidget({Key? key, this.child}) : super(key: key);

 final Widget? child;

 @override
 SingleChildRenderObjectElement createElement() =>
   SingleChildRenderObjectElement(this);
}

回到 Opacity 小部件,它公开了为这个特定小部件创建和更新 RenderObject 的两个基本方法。

 @override
 RenderOpacity createRenderObject(BuildContext context) {
  return RenderOpacity(
   opacity: opacity,
   alwaysIncludeSemantics: alwaysIncludeSemantics,
  );
 }
 @override
 void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
  renderObject
   ..opacity = opacity
   ..alwaysIncludeSemantics = alwaysIncludeSemantics;
 }

createRenderObject方法返回RenderOpacity类。的RenderOpacity类需要在配置参数,这是不透明度在0.0和1.0之间的范围内。

RenderOpacity 扩展了RenderProxyBox 类,它提供了对子部件执行不同操作的方法——其中最重要的是paint()方法。

@override
 void paint(PaintingContext context, Offset offset) {
  if (child != null) {
   if (_alpha == 0) {
    layer = null;
    return;
   }
   if (_alpha == 255) {
    layer = null;
    context.paintChild(child!, offset);
    return;
   }
   assert(needsCompositing);
   layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?);
  }
 }

如何理解Flutter中的RenderObject?Paint 方法执行必要的检查和断言,然后使用context.pushOpacity. 这就是主要操作发生的地方,所以即使我们有 Opacity 小部件及其相应的元素,绘制发生在 RenderObjects 中。它们在 Flutter 表面之下发生的事情的过程中是非常重要的。

现在我们已经了解了 RenderObjects,让我们看看如何使用自定义 RenderObjects 创建小部件以满足我们的需求。

如何创建自己的RenderObject

Flutter如何使用RenderObject?本节将介绍创建自定义小部件的分步过程 - 我们将创建一个 Gap 小部件 - 及其 RenderObject,它将负责在屏幕上绘制布局。

间隙小部件是在树中的小部件之间创建空间或间隙的小部件。与SizedBox 类不同,Gap 不需要连续设置大小,而是推断它应该是什么大小。它通过检查其父级的布局,然后根据布局创建间隙来做到这一点。

Gap 小部件只接受一个属性,mainAxisExtent即我们需要的小部件之间的空间量。

Flutter RenderObject用法示例:我们需要做的第一件事是创建 RenderObject,它将执行实际的布局_RenderGap。它 extends RenderBox,它扩展了 RenderObject。(另一种是RenderSliver, 当我们需要可滚动的内容时使用。)

abstract class RenderBox extends RenderObject {  

所述_RenderGap接受传递的值,并将其设置到mainAxisExtent参数; 它还调用该markNeedsLayout()方法,该方法告诉 Flutter 特定值已更改,Flutter 需要再次运行performLayout()方法。

class _RenderGap extends RenderBox {
_RenderGap({
 double? mainAxisExtent,
 }) : _mainAxisExtent = mainAxisExtent!;
 double get mainAxisExtent => _mainAxisExtent;
 double _mainAxisExtent;
 set mainAxisExtent(double value) {
  if (_mainAxisExtent != value) {
   _mainAxisExtent = value;
   markNeedsLayout();
  }
 }
 @override
 void performLayout() {
  final AbstractNode flex = parent!;
  if (flex is RenderFlex) {
   if (flex.direction == Axis.horizontal) {
    size = constraints.constrain(Size(mainAxisExtent, 0));
   } else {
    size = constraints.constrain(Size(0, mainAxisExtent));
   }
  } else {
   throw FlutterError(
    'Gap widget is not inside a Flex Parent',
   );
  }
 }
}

performLayout方法做了两个关键的事情:

  1. 检查父级的布局方向
  2. 根据这些结果,它通过调用垂直或水平方向的约束来设置 Gap 小部件的大小

然后我们可以继续创建 Gap 小部件,它将使用这个 RenderObject。

class Gap extends LeafRenderObjectWidget {
 const Gap(
  this.mainAxisExtent, {
  Key? key,
 }) : assert(mainAxisExtent >= 0 && mainAxisExtent < double.infinity),
    super(key: key);
 final double mainAxisExtent;
}

回想一下我们之前提到的基于子组件的数量应该扩展哪些小部件;由于 Gap 小部件不接受任何孩子,它扩展了 LeafRenderObjectWidget,接受并对其执行两项检查:mainAxisExtent value

  1. 检查它是否大于零——我们不希望应用程序中有负间距,而这个检查消除了这种可能性。如果我们的值小于零,Flutter 会抛出异常
  2. 检查值是否小于- 我们不想要一个永远持续的 Gap 空间double.infinity

Gap 小部件还公开了两个负责创建和更新 RenderObject 的方法(_RenderGap对我们而言):

  • createRenderObject方法返回 RenderObject,即_RenderGap,并传递mainAxisExtent我们想要的值
  • updateRenderObject方法接收_RenderGap并更新mainAxisExtent
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderGap(mainAxisExtent: mainAxisExtent);
}
@override
void updateRenderObject(BuildContext context, _RenderGap renderObject) {
renderObject.mainAxisExtent = mainAxisExtent;
}

我们已经成功设置了 Gap 小部件!现在,让我们构建一个简单的 UI 来在实践中展示它。

在实践中使用我们的 Gap 小部件

Gap 小部件使用我们指定的尺寸为我们的 UI 添加间距 例如,如果我们当前位于 Column 小部件中,则 Gap 小部件会推断其父级(列小部件)具有垂直方向,因此,在绘制期间,它会进行布局本身在垂直方向,这意味着它创造了一个垂直的空间。如果父小部件具有水平方向,则它在水平方向上布局。

Flutter如何使用RenderObject?让我们构建一个简单的屏幕来显示它的运行情况。我们的屏幕将同时具有 Row 和 Column 小部件,并且每个小部件中都有一个 Gap 小部件,因此我们可以看到它如何响应垂直和水平布局。

import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
 /// Creates a [HomePage].
 const HomePage({
  Key? key,
 }) : super(key: key);
 @override
 Widget build(BuildContext context) {
  return Scaffold(
   body: SafeArea(
    child: Padding(
     padding: const EdgeInsets.all(16.0),
     child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisAlignment: MainAxisAlignment.center,
      mainAxisSize: MainAxisSize.max,
      children: <Widget>[
       const Text('This is testing the Gap widget'),
       const Gap(30),
       const Text(
         'Notice the gap between me and the text above me, its vertical'),
       const Gap(30),
       const Text('Now lets look at it working horizontally'),
       const Gap(16),
       Row(
        children: const [
         Text('First Text inside the Row'),
         Gap(16),
         Text(
          'Second Text inside Row',
          maxLines: 3,
         ),
        ],
       ),
      ],
     ),
    ),
   ),
  );
 }
}

我们传入我们想要的间距值,而不指定它是水平空间还是垂直空间;间隙小部件应检查父小部件的方向,并相应地将间隙空间呈现为水平或垂直。

保存并运行你的应用程序。你应该会看到差距及其对应用程序布局的影响。

如何理解Flutter中的RenderObject ?详细解析
Flutter RenderObject用法示例

在我的 GitHub 上查看完整代码

如果你不想自己编写,你还可以下载将提供 Gap 小部件的软件包。但是,从头开始构建一个结构可以为你提供更好的结构灵活性,因为你可以对其进行调整以适应你的需求。它还可以帮助你更好地了解整个过程以及它是如何组合在一起形成小部件的。

结论

呼,我们做到了!我们成功地创建了我们的 RenderObject 并用它来构建一个小部件来满足我们的需求(好吧,它让生活更轻松——你会同意)。如何理解Flutter中的RenderObject?希望你已经成功了解了 Flutter 中的 RenderObject,它的用途是什么,以及它们如何帮助构建小部件,为我们提供应用程序所需的特殊功能。

最重要的是,你已经了解了 Flutter 应用程序表面之下发生的事情,在小部件的世界中。本文为你提供了成为世界级开发人员所需的另一种工具。使用它,你会看到效果。祝你有美好的一天。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: