详解Flutter Widget

目录
  • 概述:
    • Widget的本质:
  • 分类:
  • Widget
  • StatelessWidget
  • StatefulWidget
  • State
  • ParentDataWidget
  • RenderObjectWidget
  • 小结

概述:

所有的一切都可以被称为widget

在开发 Flutter 应用过程中,接触最多的无疑就是Widget,是『描述』 Flutter UI 的基本单元,通过Widget可以做到:

  • 描述 UI 的层级结构 (通过Widget嵌套);
  • 定制 UI 的具体样式 (如:fontcolor等);
  • 指导 UI 的布局过程 (如:paddingcenter等);

Google 在设计Widget时,还赋予它一些鲜明的特点:

  • 声明式 UI —— 相对于传统 Native 开发中的命令式 UI,声明式 UI 有不少优势,如:开发效率显著提升、UI 可维护性明显加强等;
  • 不可变性 —— Flutter 中所有Widget都是不可变的(immutable),即其内部成员都是不可变的(final),对于变化的部分需要通过「Stateful Widget-State」的方式实现;
  • 组合大于继承 —— Widget设计遵循组合大于继承这一优秀的设计理念,通过将多个功能相对单一的Widget组合起来便可得到功能相对复杂的Widget

Widget的本质:

在Widget源码中有这样一段注释:

这段注释阐明了Widget的本质:用于配置Element的,Widget本质上是 UI 的配置信息 (附带部分业务逻辑)。

我们通常会将通过Widget描述的 UI 层级结构称之为「Widget Tree」,但与「Element Tree」、「RenderObject Tree」以及「Layer Tree」相比,实质上并不存在「Widget Tree」。为了描述方便,将 Widget 组合描述的 UI 层级结构称之为「Widget Tree」,也未尝不可。

分类:

Widget

Widget,所有 Widget 的基类。

如上图所示,在 Widget基类中有 3 个重要的方法 (属性):

  • Key key —— 在同一父节点下,用作兄弟节点间的唯一标识,主要用于控制当 Widget 更新时,对应的 Element 如何处理 (是更新还是新建)。若某 Widget 是其「Parent Widget」唯一的子节点时,一般不用设置 key;

GlobalKey 是一类较特殊的 key,在介绍 Element 时会附带介绍。

  • Element createElement() —— 每个Widget都有一个与之对应的Element,由该方法负责创建,createElement可以理解为设计模式中的工厂方法,具体的Element类型由对应的Widget子类负责创建;
  • static bool canUpdate(Widget oldWidget, Widget newWidget) —— 是否可以用 new widget 修改前一帧用 old widget 生成的 Element,而不是创建新的 Element,Widget类的默认实现为:2个WidgetruntimeTypekey都相等时,返回true,即可以直接更新 (key 为 null 时,认为相等)。

上述更新流程,同样在介绍 Element 时会重点分析。

StatelessWidget

无状态-组合型 Widget,由其build方法描述组合 UI 的层级结构。在其生命周期内状态不可变。

/// A widget that does not require mutable state.
///
/// A stateless widget is a widget that describes part of the user interface by
/// building a constellation of other widgets that describe the user interface
/// more concretely. The building process continues recursively until the
/// description of the user interface is fully concrete (e.g., consists
/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s).

具体是两个方法:

  • StatelessElement createElement() ——「Stateless Widget」对应的 Element 为StatelessElement,一般情况下StatelessWidget子类不必重写该方法,即子类对应的 Element 也是StatelessElement
@override
StatelessElement createElement() => StatelessElement(this);
  • Widget build(BuildContext context) —— 算是 Flutter 体系中的核心方法之一
@protected
Widget build(BuildContext context);

以『声明式 UI』的形式描述了该组合式 Widget 的 UI 层级结构及样式信息,也是开发 Flutter 应用的主要工作『场所』。该方法在 3 种情况下被调用:

  • Widget 第一次被加入到 Widget Tree 中 (更准确地说是其对应的 Element 被加入到 Element Tree 时,即 Element 被挂载『mount』时);
  • 「Parent Widget」修改了其配置信息;
  • 该 Widget 依赖的「Inherited Widget」发生变化时。

当「Parent Widget」或 依赖的「Inherited Widget」频繁变化时,build方法也会频繁被调用。因此,提升build方法的性能就显得十分重要,Flutter 官方给出了几点建议:

  • *减少不必要的中间节点,即减少 UI 的层级,*如:对于「Single Child Widget」,没必要通过组合「Row」、「Column」、「Padding」、「SizedBox」等复杂的 Widget 达到某种布局的目标,或许通过简单的「Align」、「CustomSingleChildLayout」即可实现。又或者,为了实现某种复杂精细的 UI 效果,不一定要通过组合多个「Container」,再附加「Decoration」来实现,通过 「CustomPaint」自定义或许是更好的选择;
  • *尽可能使用const Widget,*为 Widget 提供const构造方法;

关于 const constructor 可以看看我这篇文章。

  • 必要时,*可以将「Stateless Widget」重构成「Stateful Widget」,*以便可以使用「Stateful Widget」中一些特定的优化手法,如:缓存「sub trees」的公共部分,并在改变树结构时使用GlobalKey
  • *尽量减小 rebuilt 范围,*如:某个 Widget 因使用了「Inherited Widget」,导致频繁 rebuilt,可以将真正依赖「Inherited Widget」的部分提取出来,封装成更小的独立 Widget,并尽量将该独立 Widget 推向树的叶子节点,以便减小 rebuilt 时受影响的范围。

StatefulWidget

有状态-组合型 Widget,但要注意的是StatefulWidget本身还是不可变的,其可变状态存在于State中。

/// A widget that has mutable state.
///
/// State is information that (1) can be read synchronously when the widget is
/// built and (2) might change during the lifetime of the widget. It is the
/// responsibility of the widget implementer to ensure that the [State] is
/// promptly notified when such state changes, using [State.setState].

具体有两个方法:

  • StatefulElement createElement() ——「Stateful Widget」对应的 Element 为StatefulElement,一般情况下StatefulWidget子类不用重写该方法,即子类对应的Element 也是StatefulElement
@override
StatefulElement createElement() => StatefulElement(this);
  • State createState() —— 创建对应的 State,该方法在StatefulElement的构造方法中被调用。可以简单地理解为当「Stateful Widget」被添加到 Widget Tree 时会调用该方法。
@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin
StatefulElement(StatefulWidget widget)
     : _state = widget.createState(),
       super(widget) {
   _state._element = this;
   _state._widget = widget;
 }

实际上是「Stateful Widget」对应的「Stateful Element」被添加到 Element Tree 时,伴随「Stateful Element」的初始化,createState方法被调用。从后文可知一个 Widget 实例可以对应多个 Element 实例 (也就是同一份配置信息 (Widget) 可以在 Element Tree 上不同位置配置多个 Element 节点),因此,createState方法在「Stateful Widget」生命周期内可能会被调用多次。
另外,需要注意的是配有GlobalKey的 Widget 对应的 Element 在整个 Element Tree 中只有一个实例。

State

有状态小部件 的逻辑和Stateful Widget

State 用于处理「Stateful Widget」的业务逻辑以及可变状态
由于其内部状态是可变的,故 State 有较复杂的生命周期:

如上图,State 的生命周期大致可以分为 8 个阶段:

  • 在对应的「Stateful Element」被挂载 (mount) 到树上时,通过StatefulElement.constructor –> StatefulWidget.createState创建 State 实例;

StatefulElement.constructor中的_state._element = this;可知,State._emelent指向了对应的 Element 实例,而我们熟知的State.context引用的就是这个_elementBuildContext get context => _element;
State实例与Element实例间的绑定关系一经确定,在整个生命周期内不会再变了 (Element 对应的 Widget 可能会变,但对应的 State 永远不会变),期间,Element可以在树上移动,但上述关系不会变 (即「Stateful Element」是带着状态移动的)。

  • StatefulElement 在挂载过程中接着会调用State.initState,子类可以重写该方法执行相关的初始化操作 (此时可以引用contextwidget属性);
  • 同样在挂载过程中会调用State.didChangeDependencies,该方法在 State 依赖的对象 (如:「Inherited Widget」) 状态发生变化时也会被调用,*子类很少需要重写该方法,*除非有非常耗时不宜在build中进行的操作,因为在依赖有变化时build方法也会被调用;
  • 此时,State 初始化已完成,其build方法此后可能会被多次调用,在状态变化时 State 可通过setState方法来触发其子树的重建;
  • 此时,「element tree」、「renderobject tree」、「layer tree」已构建完成,完整的 UI 应该已呈现出来。此后因为变化,「element tree」中「parent element」可能会对树上该位置的节点用新配置 (Widget) 进行重建,当新老配置 (oldWidget、newWidget)具有相同的「runtimeType」&&「key」时,framework 会用 newWidget 替换 oldWidget,并触发一系列的更新操作 (在子树上递归进行)。同时,State.didUpdateWidget方法被调用,子类重写该方法去响应 Widget 的变化;

上述 3 棵树以及更新流程在后续文章中会有详细介绍

  • 在 UI 更新过程中,任何节点都有被移除的可能,State 也会随之移除,(如上一步中「runtimeType」||「key」不相等时)。此时会调用State.deactivate方法,由于被移除的节点可能会被重新插入树中某个新的位置上,故子类重写该方法以清理与节点位置相关的信息 (如:该 State 对其他 element 的引用)、同时,不应在该方法中做资源清理;

重新插入操作必须在当前帧动画结束之前

  • 当节点被重新插入树中时,State.build方法被再次调用;
  • 对于在当前帧动画结束时尚未被重新插入的节点,State.dispose方法被执行,State 生命周期随之结束,此后再调用State.setState方法将报错。子类重写该方法以释放任何占用的资源。

至此,State 中的核心方法基本都已在上述过程中介绍了,下面重点看一下setState方法:

void setState(VoidCallback fn) {
  assert(fn != null);
  assert(() {
    if (_debugLifecycleState == _StateLifecycle.defunct) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    return true;
  }());
  final dynamic result = fn() as dynamic;
  assert(() {
    if (result is Future) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    return true;
  }());
  _element.markNeedsBuild();
}
  • 从上述源码可以看到,关于setState方法有几点值得关注:
  • State.dispose后不能调用setState (第 4 行);
  • 在 State 的构造方法中不能调用setState (第 7 行);
  • setState方法的回调函数 (fn) 不能是异步的 (返回值为Future),原因很简单,因为从流程设计上 framework 需要根据回调函数产生的新状态去刷新 UI (第 14 行);
  • 通过setState方法之所以能更新 UI,是在其内部调用_element.markNeedsBuild()实现的 (具体过程在介绍 Element 时再详细分析)。

关于 State 最后再强调 2 点:

  • 若State.build方法依赖了自身状态会变化的对象,如:ChangeNotifier、Stream或其他可以被订阅的对象,需要确保在initState、didUpdateWidget、dispose

等 3 方法间有正确的订阅 (subscribe) 与取消订阅 (unsubscribe) 的操作:

initState中执行 subscribe;

如果关联的「Stateful Widget」与订阅有关,在didUpdateWidget中先取消旧的订阅,再执行新的订阅;

dispose中执行 unsubscribe。

  • State.initState方法中不能调用BuildContext.dependOnInheritedWidgetOfExactType,但State.didChangeDependencies会随之执行,在该方法中可以调用。

ParentDataWidget

ParentDataWidget以及下面要介绍的InheritedElement都继承自ProxyWidget,由于ProxyWidget作为抽象基类本身没有任何功能,故下面直接介绍ParentDataWidgetInheritedElement

/// Base class for widgets that hook [ParentData] information to children of/// [RenderObjectWidget]s.

ParentDataWidget作为 Proxy 型 Widget,其功能主要是为其他 Widget 提供ParentData信息。虽然其 child widget 不一定是 RenderObejctWidget 类型,但其提供的ParentData信息最终都会落地到 RenderObejctWidget 类型子孙 Widget 上。

ParentData 是『parent renderobject』在 layout『child renderobject』时使用的辅助定位信息,详细信息会在介绍 RenderObject 时介绍。

void attachRenderObject(dynamic newSlot) {
	assert(_ancestorRenderObjectElement == null);
	_slot = newSlot;
	_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
	_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
	final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
	if (parentDataElement != null)
	                                          _updateParentData(parentDataElement.widget);
}
ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() {
	Element ancestor = _parent;
	while (ancestor != null && ancestor is! RenderObjectElement)
	                                          {
		if (ancestor is ParentDataElement<RenderObjectWidget>)
		                                          return ancestor;
		ancestor = ancestor._parent;
	}
	return null;
}
void _updateParentData(ParentDataWidget<RenderObjectWidget> parentData)
                                          {
	parentData.applyParentData(renderObject);
}

上面这段代码来自RenderObjectElement,可以看到在其attachRenderObject方法第 6 行从祖先节点找ParentDataElement,如果找到就用其 Widget(ParentDataWidget) 中的 parentData 信息去设置 Render Obejct。在查找过程中如查到RenderObjectElement (第 13 行),说明当前 RenderObject 没有 Parent Data 信息。
最终会调用到ParentDataWidget.applyParentData(RenderObject renderObject),子类需要重写该方法,以便设置对应RenderObject.parentData

来看个例子,通常配合Stack使用的Positioned(继承自ParentDataWidget):

void applyParentData(RenderObject renderObject) {
	assert(renderObject.parentData is StackParentData);
	final StackParentData parentData = renderObject.parentData;
	bool needsLayout = false;
	if (parentData.left != left)
	  {
		parentData.left = left;
		needsLayout = true;
	}
	...  if (parentData.width != width)
	  {
		parentData.width = width;
		needsLayout = true;
	}
	...  if (needsLayout) {
		final AbstractNode targetParent = renderObject.parent;
		if (targetParent is RenderObject)
		      targetParent.markNeedsLayout();
	}
}

可以看到,Positioned在必要时将自己的属性赋值给了对应的RenderObject.parentData (此处是StackParentData),并对「parent render object」调用markNeedsLayout(第 19 行),以便重新 layout,毕竟修改了布局相关的信息。

abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidget

如上所示,ParentDataWidget在定义上使用了泛型<T extends RenderObjectWidget>,其背后的含义是:
从当前ParentDataWidget节点向上追溯形成的祖先节点链(『parent widget chain』)上,在 2 个ParentDataWidget类型的节点形成的链上至少要有一个『RenderObject Widget』类型的节点。因为一个『RenderObject Widget』不能接受来自 2 个及以上『ParentData Widget』的信息。

/// Base class for widgets that efficiently propagate information down the tree.
// To obtain the nearest instance of a particular type of inherited widget from
///a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].

InheritedWidget 用于在树上向下传递数据。
通过BuildContext.dependOnInheritedWidgetOfExactType可以获取最近的「Inherited Widget」,需要注意的是通过这种方式获取「Inherited Widget」时,当「Inherited Widget」状态有变化时,会导致该引用方 rebuild。

具体原理在介绍 Element 时会详细分析。

通常,为了使用方便会「Inherited Widget」会提供静态方法of,在该方法中调用BuildContext.dependOnInheritedWidgetOfExactTypeof方法可以直接返回「Inherited Widget」,也可以是具体的数据。

有时,「Inherited Widget」是作为另一个类的实现细节而存在的,其本身是私有的(外部不可见),此时of方法就会放到对外公开的类上。最典型的例子就是Theme,其本身是StatelessWidget类型,但其内部创建了一个「Inherited Widget」:_InheritedThemeof方法就定义在上Theme上:

static MediaQueryData of(BuildContext context, {
	bool nullOk = false })
	{
		final MediaQuery query = context.dependOnInheritedWidgetOfExactType<MediaQuery>();
		if (query != null)
		  return query.data;
		if (nullOk)    return null;
	}

of方法返回的是ThemeData类型的具体数据,并在其内部首先调用了BuildContext.dependOnInheritedWidgetOfExactType

我们经常使用的「Inherited Widget」莫过于MediaQuery,同样提供了of方法:

  • InheritedElement createElement() ——「Inherited Widget」对应的 Element 为InheritedElement,一般情况下InheritedElement子类不用重写该方法;
  • bool updateShouldNotify(covariant InheritedWidget oldWidget) —— 在「Inherited Widget」rebuilt 时判断是否需要 rebuilt 那些依赖它的 Widget;

如下是MediaQuery.updateShouldNotify的实现,在新老Widget.data 不相等时才 rebuilt 那依赖的 Widget。

bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;

RenderObjectWidget

真正与渲染相关的 Widget,属于最核心的类型,一切其他类型的 Widget 要渲染到屏幕上,最终都要回归到该类型的 Widget 上。

  • RenderObjectElement createElement() ——「RenderObject Widget」对应的 Element 为RenderObjectElement,由于RenderObjectElement也是抽象类,故子类需要重写该方法;
  • RenderObject createRenderObject(BuildContext context) —— 核心方法,创建 Render Widget 对应的 Render Object,同样子类需要重写该方法。该方法在对应的 Element 被挂载到树上时调用(Element.mount),即在 Element 挂载过程中同步构建了「Render Tree」(详细过程后续文章会详细分析);
@overrideRenderFlex createRenderObject(BuildContext context) {
	return RenderFlex(
	  direction: direction,
	  mainAxisAlignment: mainAxisAlignment,
	  mainAxisSize: mainAxisSize,
	  crossAxisAlignment: crossAxisAlignment,
	  textDirection: getEffectiveTextDirection(context),
	  verticalDirection: verticalDirection,
	    textBaseline: textBaseline,
	  )
	;
}

上面是Flex.createRenderObject的源码,真实感受一下 (还是代码更有感觉)。可以看到,用Flex的信息(配置)初始化了RenderFlex

FlexRowColumn的基类,RenderFlex继承自RenderBox,后者继续自RenderObject

  • void updateRenderObject(BuildContext context, covariant RenderObject renderObject) —— 核心方法,在 Widget 更新后,修改对应的 Render Object。该方法在首次 build 以及需要更新 Widget 时都会调用;
@overridevoid updateRenderObject(BuildContext context, covariant RenderFlex renderObject)
{
	renderObject
	  ..
	direction = direction
	  ..mainAxisAlignment = mainAxisAlignment
	  ..
	mainAxisSize = mainAxisSize
	  ..
	crossAxisAlignment = crossAxisAlignment
	  ..
	textDirection = getEffectiveTextDirection(context)
	  ..
	verticalDirection = verticalDirection
	  ..
	textBaseline = textBaseline;
}

Flex.updateRenderObject的源码也很简单,与Flex.createRenderObject几乎一一对应,用当前Flex的信息修改renderObject

  • void didUnmountRenderObject(covariant RenderObject renderObject) —— 对应的「Render Object」从「Render Tree」上移除时调用该方法。

RenderObjectWidget的几个子类:LeafRenderObjectWidgetSingleChildRenderObjectWidgetMultiChildRenderObjectWidget只是重写了createElement方法以便返回各自对应的具体的 Element 类实例。

小结



至此,重要的基础型 Widget 基本介绍完了,总结一下:

  • Widget 本质上是 UI 的配置信息 (附加部分业务逻辑),并不存在一颗真实的「Widget Tree」(与「Element Tree」、「RenderObject Tree」以及「Layer Tree」相比);
  • Widget 从功能上可以分为 3 类:「Component Widget」、「Proxy Widget」以及「Renderer Widget」;
  • Widget 与 Element 一一对应,Widget 提供创建 Element 的方法 (createElement,本质上是一个工厂方法);
  • 只有「Renderer Widget」才会参与最终的 UI 生成过程(Layout、Paint),只有该类型的 Widget 才有与之对应的「Render Object」,同样由其提供创建方法(createRenderObject)。

到此这篇关于详解Flutter Widget的文章就介绍到这了,更多相关Flutter Widget内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • flutter传递值到任意widget(当需要widget嵌套使用需要传递值的时候)

    如果我们有这样一个应用场景: WidgetA执行点击之后将数据通过widgetB传递到其下的widgetC. 通常可以通过设置构造函数,传递对应参数到制定的widget树中,如下面代码所描述: 表示需要将widgetA中的点击改变内容传递到widgetB中的widgetC中展示: 需要通过设置widgetB的构造函数,接收对应参数,再传递给widgetC展示: class Inheritedwidget extends StatefulWidget { @override _InheritedW

  • Flutter中获取屏幕及Widget的宽高示例代码

    前言 我们平时在开发中的过程中通常都会获取屏幕或者 widget 的宽高用来做一些事情,在 Flutter 中,我们有两种方法来获取 widget 的宽高. MediaQuery 一般情况下,我们会使用如下方式去获取 widget 的宽高: final size =MediaQuery.of(context).size; final width =size.width; final height =size.height; 但是如果不注意,这种写法很容易报错,例如下面的写法就会报错: impor

  • Flutter开发之Widget自定义总结

    前言 在Flutter实际开发中,大家可能会遇到flutter框架中提供的widget达不到我们想要的效果,这时就需要我们去自定义widget,从Flutter构建.布局.绘制三部曲中我们了解到,实际的测量.布局.绘制操作都在RenderObject中,我们是可以进行继承相关的RenderObject来实现自定义的.但是其实flutter框架在设计之初就给我们预留出了自定义的入口,方便我们进行自定义. CustomPaint自定义绘制 例:圆形进度条 思路:使用CustomPaint绘制需要的效

  • 详解Flutter Widget

    目录 概述: Widget的本质: 分类: Widget StatelessWidget StatefulWidget State ParentDataWidget RenderObjectWidget 小结 概述: 所有的一切都可以被称为widget 在开发 Flutter 应用过程中,接触最多的无疑就是Widget,是『描述』 Flutter UI 的基本单元,通过Widget可以做到: 描述 UI 的层级结构 (通过Widget嵌套): 定制 UI 的具体样式 (如:font.color等

  • 详解Flutter点击空白隐藏键盘的全局做法

    开发原生页面的时候,在处理键盘事件上,通常的需求是,点击输入框外屏幕,要隐藏键盘,同样的,这样的需求也需要在 Flutter 上实现, Android 上的实现方式是在基类 Activity 里实现事件分发,判断触摸位置是否在输入框内. /** * 获取点击事件 */ @CallSuper @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.MotionEv

  • 详解Flutter 调用 Android Native 的方法

    Flutter 调用 Android Native 的方法,是通过MethodChannel的方式来实现的: 在Android端: 创建一个Class,实现FlutterPlugin和MethodCallHandler接口 重写onAttachedToEngine(),onDetachedFromEngine(),onMethodCall() onAttachedToEngine()中,根据自定义的CHANNEL_NAME创建MethodChannel, onDetachedFromEngine

  • 详解Flutter的路由导航

    Flutter 的路由导航 路由管理或导航管理:从一个页面平滑地过渡到另一个页面,我们需要有一个统一的机制来管理页面之间的跳转.在原生的Android 开发,是通过startActivity或startActivityForResult 来完成页面的跳转的,在Flutter 中如何实现呢? 在 Flutter 中,页面之间的跳转是通过 Route 和 Navigator 来管理的: Route 是页面的抽象,主要负责创建对应的界面,接收参数,响应 Navigator 打开和关闭: 而 Navig

  • 详解Flutter中视频播放器插件的使用教程

    目录 创建一个新的视频播放器 添加播放和暂停按钮 创建一个快进 添加一个视频进度指示器 应用视频的字幕 结论 您已经看到很多包含视频内容的应用程序,比如带有视频教程的食谱应用程序.电影应用程序和体育相关的应用程序.您是否想知道如何将视频内容添加到您的下一个Flutter应用程序中? 从头开始实现视频功能将是一项繁重的任务.但有几个插件可以让开发者的生活变得轻松.视频播放器插件是可用于 Flutter 的最佳插件之一,可满足这一要求. 在这篇文章中,您将学习如何应用视频播放器插件以及控制视频播放器

  • 详解Flutter和Dart取消Future的三种方法

    目录 使用异步包(推荐) 完整示例 使用 timeout() 方法 快速示例 将Future转换为流 快速示例 结论 使用异步包(推荐) async包由 Dart 编程语言的作者开发和发布.它提供了dart:async风格的实用程序来增强异步计算.可以帮助我们取消Future的是CancelableOperation类: var myCancelableFuture = CancelableOperation.fromFuture( Future<T> inner, { FutureOr on

  • 详解Flutter如何读写文本文件

    目录 介绍 示例 1:加载内容 预览 完整代码 示例 2: Reading and Writing 获取文件路径 示例预览 完整的代码和解释 介绍 文本文件(具有 .txt扩展名)广泛用于持久存储信息,从数字数据到长文本.今天,我将介绍 2 个使用此文件类型的 Flutter 应用程序示例. 第一个示例快速而简单.它仅使用 rootBundle(来自 services.dart)从 assets 文件夹(或根项目中的另一个文件夹)中的文本加载内容,然后将结果输出到屏幕上.当您只需要读取数据而不需

  • 详解Flutter中的数据传递

    目录 Flutter 中的数据传递 InheritedWidget EventBus 总结 Flutter 中的数据传递 在开发中,数据从一个页面传递到另一个页面事很常用的,在Android 开发中,通常是通过把数据放到 intent 中传递过去.在 Flutter 中,数据是如何传递的呢? 在Flutter 中一切都是Widget,所以数据的传递就成了数据才Widget 中的传递.在之前的学习中,数据从一个Widget 传递到 子 Widget 是通过构造函数,一层一层的往里面传,要是 wid

  • 详解Flutter如何完全自定义TabBar

    目录 前言 实现过程 完整代码 总结 前言 在App中TabBar形式交互是非常常见的,但是系统提供的的样式大多数又不能满足我们产品和UI的想法,这篇就记录下在Flutter中我在实现自定义TabBar的一个思路和过程,希望对你也有所帮助~ 先看下我最终的效果图: 实现过程 首先我们先看下TabBar的构造方法: const TabBar({ Key? key, required this.tabs,// tab组件列表 this.controller,// tabBar控制器 this.isS

  • 详解Flutter中数据传递的方式

    目录 1.构造方法传递 2.InheritedWidget 3.Notification 4.Stream & event_bus 在Flutter中,常见的数据传递一共有以下几种: 1.构造方法传递 Flutter的构造方法具备着dart语言的特点,参数具备可选状态,通过构造方法传递数据,可以很方便的将任意数据进行传递,平时开发中,A跳转B页面最常用的方法就是通过构造方法进行传递.比如我们最常见的Key就是通过构造一级一级向下传递的. 优点: 相邻页面之间传递数据非常方便,你不需要进行任何额外

随机推荐