精华内容
下载资源
问答
  • Widget
    千次阅读
    2022-03-05 22:54:07

     widget控件

    所谓widget,其实可以理解为是kcontrol的进一步升级和封装,它同样是指音频系统中的某个部件,比如mixer,mux,输入输出引脚,电源供应器等等,甚至,我们可以定义虚拟的widget,例如playback stream widget。widget把kcontrol和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个widget可以与它相邻的widget有某种动态的连结关系。在DAPM框架中,widget用结构体snd_soc_dapm_widget来描述(部分):

    struct snd_soc_dapm_widget {
            enum snd_soc_dapm_type id;
            const char *name;               /* widget name */
     
            ......
            /* dapm control */
            int reg;                        /* negative reg = no direct dapm */
            unsigned char shift;            /* bits to shift */
            unsigned int value;             /* widget current value */
            unsigned int mask;              /* non-shifted mask */
            ......
     
            int (*power_check)(struct snd_soc_dapm_widget *w);
     
            int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
     
            /* kcontrols that relate to this widget */
            int num_kcontrols;
            const struct snd_kcontrol_new *kcontrol_news;
            struct snd_kcontrol **kcontrols;
     
            /* widget input and outputs */
            struct list_head sources;
            struct list_head sinks;
            ......
    };

    下面介绍各成员:

    .id:该widget的类型值,比如snd_soc_dapm_output,snd_soc_dapm_mixer等等
    .name:该widget的名字
    .sname:代表该widget所在stream的名字,比如对于snd_soc_dapm_dai_in类型的widget,会使用该字段
    .codec .platform:指向该widget所属的codec和platform
    .list:所有注册到系统中的widget都会通过该list,链接到代表声卡的snd_soc_card结构的widgets链表头字段中
    .dapm:snd_soc_dapm_context结构指针,ASoC把系统划分为多个dapm域,每个widget属于某个dapm域,同一个域代表着同样的偏置电压供电策略比如,同一个codec中的widget通常位于同一个dapm域,而平台上的widget可能又会位于另外一个dapm域中
    .priv:有些widget可能需要一些专有的数据,可以使用该字段来保存,像snd_soc_dapm_dai_in类型的widget,会使用该字段来记住与之相关联的snd_soc_dai结构指针
    .regulator:对于snd_soc_dapm_regulator_supply类型的widget,该字段指向与之相关的regulator结构指针
    .params:目前对于snd_soc_dapm_dai_link类型的widget,指向该dai的配置信息的 snd_soc_pcm_stream结构
    .reg .shift .mask:这3个字段用来控制该widget的电源状态,分别对应控制信息所在的寄存器地址,位移值和屏蔽值
    .value .on_val .off_val:电源状态的当前值,开启时和关闭时所对应的值
    .power .invert:用于指示该widget当前是否处于上电状态,invert则用于表明power字段是否需要逻辑反转
    .active .connected:分别表示该widget是否处于激活状态和连接状态,当和相邻的widget有连接关系时,connected位会被置1,否则置0
    .new:我们定义好的widget(snd_soc_dapm_widget结构),在注册到声卡中时需要进行实例化,该字段用来表示该widget是否已经被实例化
    .ext:表示该widget当前是否有外部连接,比如连接mic,耳机,喇叭等等
    .force:该位被设置后,将会不管widget当前的状态,强制更新至新的电源状态
    .ignore_suspend new_power .power_checked:这些电源管理相关的字段
    .subseq:该widget目前在上电或下电队列中的排序编号,为了防止在上下电的过程中出现pop-pop声,DAPM会给每个widget分配合理的上下电顺序
    .power_check:用于检查该widget是否应该上电或下电的回调函数指针
    .event_flags:该字段是一个位或字段,每个位代表该widget会关注某个DAPM事件通知。只有被关注的通知事件会被发送到widget的事件处理回调函数中
    .event:DAPM事件处理回调函数指针
    .num_kcontrols .kcontrol_news .kcontrols:这3个字段用来描述与该widget所包含的kcontrol控件,例如一个mixer控件或者是一个mux控件
    .sources sinks:两个链表字段,两个widget如果有连接关系,会通过一个snd_soc_dapm_path结构进行连接,sources链表用于链接所有的输入path,sinks链表用于链接所有的输出path
    .power_list:每次更新整个dapm的电源状态时,会根据一定的算法扫描所有的widget,然后把需要变更电源状态的widget利用该字段链接到一个上电或下电的链表中,扫描完毕后,dapm系统会遍历这两个链表执行相应的上电或下电操作
    .dirty:链表字段,widget的状态变更后,dapm系统会利用该字段,把该widget加入到一个dirty链表中,稍后会对dirty链表进行扫描,以执行整个路径的更新
    .inputs:该widget的所有有效路径中,连接到输入端的路径数量
    .outputs:该widget的所有有效路径中,连接到输出端的路径数量
    .clk:对于snd_soc_dapm_clock_supply类型的widget,指向相关联的clk结构体(指针)

    widget的种类

    在DAPM框架中,把各种不同的widget划分为不同的种类,可选的种类都定义在一个枚举中:

    /* dapm widget types */
    enum snd_soc_dapm_type {......}

    下面我们逐个解释一下这些widget的种类:

    snd_soc_dapm_input 该widget对应一个输入引脚。
    snd_soc_dapm_output 该widget对应一个输出引脚。
    snd_soc_dapm_mux 该widget对应一个mux控件。
    snd_soc_dapm_virt_mux 该widget对应一个虚拟的mux控件。
    snd_soc_dapm_value_mux 该widget对应一个value类型的mux控件。
    snd_soc_dapm_mixer 该widget对应一个mixer控件。
    snd_soc_dapm_mixer_named_ctl 该widget对应一个mixer控件,但是对应的kcontrol的名字不会加入widget的名字作为前缀。
    snd_soc_dapm_pga 该widget对应一个pga控件(可编程增益控件)。
    snd_soc_dapm_out_drv 该widget对应一个输出驱动控件
    snd_soc_dapm_adc 该widget对应一个ADC
    snd_soc_dapm_dac 该widget对应一个DAC
    snd_soc_dapm_micbias 该widget对应一个麦克风偏置电压控件
    snd_soc_dapm_mic 该widget对应一个麦克风
    snd_soc_dapm_hp 该widget对应一个耳机
    snd_soc_dapm_spk 该widget对应一个扬声器
    snd_soc_dapm_line 该widget对应一个线路输入
    snd_soc_dapm_switch 该widget对应一个模拟开关
    snd_soc_dapm_vmid 该widget对应一个codec的vmid偏置电压
    snd_soc_dapm_pre machine级别的专用widget,会先于其它widget执行检查操作 snd_soc_dapm_post machine级别的专用widget,会后于其它widget执行检查操作 snd_soc_dapm_supply 对应一个电源或是时钟源
    snd_soc_dapm_regulator_supply 对应一个外部regulator稳压器
    snd_soc_dapm_clock_supply 对应一个外部时钟源
    snd_soc_dapm_aif_in 对应一个数字音频输入接口,比如I2S接口的输入端 snd_soc_dapm_aif_out 对应一个数字音频输出接口,比如I2S接口的输出端 snd_soc_dapm_siggen 对应一个信号发生器
    snd_soc_dapm_dai_in 对应一个platform或codec域的输入DAI结构
    snd_soc_dapm_dai_out 对应一个platform或codec域的输出DAI结构
    snd_soc_dapm_dai_link 用于链接一对输入/输出DAI结构

    widget的定义

    和普通的kcontrol一样,DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件,这些宏定义根据widget的类型,按照它们的电源所在的域,被分为了几个域,他们分别是:

    codec域 比如VREF和VMID等提供参考电压的widget,这些widget通常在codec的probe/remove回调中进行控制,当然,在工作中如果没有音频流时,也可以适当地进行控制它们的开启与关闭。

    platform域 位于该域上的widget通常是针对平台或板子的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风,因为这些接口在每块板子上都可能不一样,所以通常它们是在machine驱动中进行定义和控制,并且也可以由用户空间的应用程序通过某种方式来控制它们的打开和关闭。

    音频路径域 一般是指codec内部的mixer、mux等控制音频路径的widget,这些widget可以根据用户空间的设定连接关系,自动设定他们的电源状态。

    音频数据流域 是指那些需要处理音频数据流的widget,例如ADC、DAC等等。

    举个例子

    #define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
             wcontrols, wncontrols)\
    {       .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
            .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}

    可以发现这个widget的reg和shift字段是需要赋值的,说明这个widget是有相应的电源控制寄存器的,DAPM框架在扫描和更新音频路径时,会利用这些寄存器来控制widget的电源状态,使得它们的供电状态是按需分配的,需要的时候(在有效的音频路径上)上电,不需要的时候(不再有效的音频路径上)下电。这些widget需要完成和之前介绍的mixer、mux等控件同样的功能,实际上,这是通过它们包含的kcontrol控件来完成的,这些kcontrol我们需要在定义widget前先定义好,然后通过wcontrols和num_kcontrols参数传递给这些辅助定义宏。 如果需要自定义这些widget的dapm事件处理回调函数,也可以使用下面这些带“_E”后缀的版本:

    • SND_SOC_DAPM_PGA_E

    • SND_SOC_DAPM_OUT_DRV_E

    • SND_SOC_DAPM_MIXER_E

    • SND_SOC_DAPM_MIXER_NAMED_CTL_E

    • SND_SOC_DAPM_SWITCH_E

    • SND_SOC_DAPM_MUX_E

    • SND_SOC_DAPM_VIRT_MUX_E

    还有另外三种widget没有提供显式的定义方法,它们的种类id分别是:

    • snd_soc_dapm_dai_in

    • snd_soc_dapm_dai_out

    • snd_soc_dapm_dai_link

    每个codec有多个dai,而cpu(通常就是指某个soc cpu芯片)也会有多个dai,dai注册时,dapm系统会为每个dai创建一个snd_soc_dapm_dai_in或snd_soc_dapm_dai_out类型的widget,通常,这两种widget会和codec中具有相同的stream name的widget进行连接。另外一种情况,当系统中具有多个音频处理器(比如多个codec)时,他们之间可能会通过某两个dai进行连接,当machine驱动确认有这种配置时(通过判断dai_links结构中的param字段),会为他们建立一个dai link把他们绑定在一起,因为有连接关系,两个音频处理器之间的widget的电源状态就可以互相传递。

    更多相关内容
  • WidgetWidget 由 修改而来, 是UI 组件的基础类,约定了组件的基本生命周期,实现了一些通用功能。基于 Widget可以构建出任何你想要的 Web 界面组件。此库依赖全局变量$, 需要jQuery或Zepto将提前$注入到全局变量中...
  • Widget与QML信息交互和控制实现跑马灯 效果: Widget与QML信息交互和控制实现跑马灯 1、渐变色彩的炫酷动画背景; 2、文字置顶,从左至右,循环跑马; 3、完整展示Widget与QML信息交互和控制,包括: 信号、槽、属性...
  • NULL 博文链接:https://hanllove001.iteye.com/blog/1185506
  • 点击按钮实现不同widget间切换位置大小
  • Widget介绍.rar

    2019-07-09 23:00:15
    Widget由来、发展、用途、示例、开发 Widget是一种小插件,通常以小窗或小框的形式出现在网页、系统桌面、手机等地方。Widget通常使用的是HTML、Javascript、Flash或者iframe方式嵌入。一个界面可以有多个widget,...
  • Widget扩展用于在页面根据需要输出不同的内容,Widget扩展的定义是在项目的Lib\Widget目录下面定义Widget类库,例如下面定义了一个用于显示最近的评论的Widget: 位于Lib\Widget\ShowCommentWidget.class.php Widget...
  • org.vaadin.addons.dcharts-widget-0.10.0-dcharts-widget-0.10.0.jar
  • Flutter Widget

    千次阅读 2021-11-17 20:30:17
    即一切为Widget,与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,...


    从flutter的架构图中不难看出widget是整个视图描述的基础,Flutter 的核心设计思想便是

    everything is a widget.

    即一切为Widget,与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,而原生开发中的控件通常只是指UI元素。

    Flutter 中的 widget 可以用两条规则来约束:

    1. 一切都是 widget。
    2. 每个 widget 只负责自己关注的部分。


    第一条意味着你所看到的东西都是由于 widget 构成,跟原生不同的是,原本在原生中一些参数相关的东西到了 Futter 中都被 widget 化,例如大小、背景、margin、padding 等等原本只需要一个参数设置的东西对应到 Flutter 中都映射成了一个单独的 widget。

    第二条的意思是,每个 widget 应该仅负责自己的职责所在,比如文本框 Text 组件,只负责如何显示一个文本,其他一概不管,不用考虑自己的大小、位置、margin 等等,这些由别的专门负责此功能的 widget 来控制,作为一个文本框就只负责文本框的职责。

    一. Widget 简介

    接下来,我们通过源码(Flutter 2.0.6)及源码的官方文档来进一步理解 Widget,我们先通过官方文档去了解Widget 的描述。

    Describes the configuration for an [Element].

    由上面的文档可以知道,在Flutter中,Widget的功能是“描述一个UI元素的配置数据”,也就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述Element的一个配置数据(本篇的关注点是 Widget,将不会过多的介绍 Element),告诉 Element 这个实例如何去渲染,接下来我们继续往下看。

    /// Widgets are the central class hierarchy in the Flutter framework. A widget
    /// is an immutable description of part of a user interface. Widgets can be
    /// inflated into elements, which manage the underlying render tree.

    Widgets是flutter框架中的最核心的类,一个widget是一部分不可变的用户界面的描述,小部件可以膨胀为元素,用于管理底层渲染树。

    /// Widgets themselves have no mutable state (all their fields must be final).
    /// If you wish to associate mutable state with a widget, consider using a
    /// [StatefulWidget], which creates a [State] object (via
    /// [StatefulWidget.createState]) whenever it is inflated into an element and
    /// incorporated into the tree.

    Widgets 本身没有可变状态(它们的所有字段都必须是final)。如果希望将可变状态与Widgets 关联,请考虑使用
    [statefulwidget],它创建一个[状态]对象(通过 [statefulWidget.createState])当它被 inflated 成一个元素时合并到树中

    /// A given widget can be included in the tree zero or more times. In particular
    /// a given widget can be placed in the tree multiple times. Each time a widget
    /// is placed in the tree, it is inflated into an [Element], which means a
    /// widget that is incorporated into the tree multiple times will be inflated
    /// multiple times.

    给定的 widget 可以包含在树中零次或多次。特别地给定的 widget 可以多次放置在树中。每次一个 widget 放在树中,它会膨胀成一个 [element],这意味着多次合并到树中的小部件将被 inflated 多次。

    /// The [key] property controls how one widget replaces another widget in the
    /// tree. If the [runtimeType] and [key] properties of the two widgets are
    /// [operator==], respectively, then the new widget replaces the old widget by
    /// updating the underlying element (i.e., by calling [Element.update] with the
    /// new widget). Otherwise, the old element is removed from the tree, the new
    /// widget is inflated into an element, and the new element is inserted into the
    /// tree.

    [key]属性控制一个小部件如何替换树中的另一个小部件。如果两个小部件的[runtimeType]和[key]属性都[operator==]相等,则新小部件将通过更新基础元素(使用新小部件调用[element.update])来替换旧小部件。否则,将从树中删除旧元素,将新小部件膨胀为元素,并将新元素插入树中。

    整理一下,widget 是用来描述如何创建 Element 的,widget 本身是一个不可变对象,它的所有字段都必须是 final,它可以被复用,请注意,这里的复用不是指在两次渲染的时候将对象从旧树中拿过来放到新树,而是在同一个 Widget Tree 中,某个子 Widget 可以出现多次,因为它只是一个 description。在一次渲染中,Flutter Framework 会调用 Widget的 createElement() 方法,这个方法会创建一个新的对应Element 对象并返回,所以即使 Widget 被重复使用,框架还是会创建多个不同的 Element 对象。

    二. Widget 属性和方法

    剩下的就是Widget的属性和说明,我们接下来继续往下看,这块将在 Widget 源码里说明

    @immutable
    abstract class Widget extends DiagnosticableTree {
      const Widget({ this.key });
      final Key? key;
        
      @protected
      @factory
      Element createElement();
    
      @override
      String toStringShort() {
        final String type = objectRuntimeType(this, 'Widget');
        return key == null ? type : '$type-$key';
      }
    
      @override
      void debugFillProperties(DiagnosticPropertiesBuilder properties) {
        super.debugFillProperties(properties);
        properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
      }
    
      @override
      @nonVirtual
      bool operator ==(Object other) => super == other;
    
      @override
      @nonVirtual
      int get hashCode => super.hashCode;
      
      static bool canUpdate(Widget oldWidget, Widget newWidget) {
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
      
      static int _debugConcreteSubtype(Widget widget) {
        return widget is StatefulWidget ? 1 :
               widget is StatelessWidget ? 2 :
               0;
        }
    }
    
    • Widget 类继承自 DiagnosticableTree,DiagnosticableTree 即“诊断树”,主要作用是提供调试信息。
    • Key : [key]属性控制一个小部件如何替换树,主要的作用是决定是否在下一次build时复用旧的widget,决定的条件在 canUpdate() 方法中。
    • createElement():正如所述“一个 Widget 可以对应多个 Element ”;Flutter Framework在构建UI树时,会先调用此方法生成对应节点的 Element 对象。此方法是Flutter Framework隐式调用的,在我们开发过程中基本不会调用到。
    • debugFillProperties(…) 复写父类的方法,主要是设置诊断树的一些特性。
    • canUpdate(…)是一个静态方法,它主要用于在 Widget 树重新 build 时复用旧的 widget,其实具体来说,应该是:是否用新的 Widget 对象去更新旧UI树上所对应的 Element 对象的配置;通过其源码我们可以看到,只要newWidget 与 oldWidget 的 runtimeType 和 key 同时相等时就会用 newWidget 去更新 Element 对象的配置,否则就会创建新的 Element。


    另外 Widget 类本身是一个抽象类,其中最核心的就是定义了 createElement() 接口,在 Flutter 开发中,我们一般都不用直接继承 Widget 类来实现一个新组件,相反,我们通常会通过继承 StatelessWidget 或StatefulWidget 来间接继承Widget类来实现。

    See also:
    ///
    /// * [StatefulWidget] and [State], for widgets that can build differently
    /// several times over their lifetime.
    /// * [InheritedWidget], for widgets that introduce ambient state that can
    /// be read by descendant widgets.
    /// * [StatelessWidget], for widgets that always build the same way given a
    /// particular configuration and ambient state.

    _/// [StatefulWidget] and [State],_在其生命周期内可以多次进行不同构建的小部件
    /// _[InheritedWidget], _引入可被后代 widget 读取的环境状态的 widget。
    _/// [StatelessWidget], _总是以相同的方式构建特定配置和环境状态的小部件


    StatelessWidget 和 StatefulWidget 都是直接继承自Widget类,而这两个类也正是 Flutter 中非常重要的两个抽象类,它们引入了两种 Widget 模型,后续我们将重点一一介绍这两个类.


    三. Widget key

    在每个widget的构造函数都有一个key参数,这个参数的作用是什么呢?**Key用于在widget的位置改变时保留其状态。**比如,保留用户的滑动位置,或者在保留widget状态的情况下修改一个widget集合,如Row、Column等。

    3.1 Key是什么?

    /// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
    ///
    /// A new widget will only be used to update an existing element if its key is
    /// the same as the key of the current widget associated with the element.
    

    Key是Widget、Element和SemanticsNode的标识符。

    只有当一个新的小部件的键与当前该元素关联的小部件的键相同时,它才会用于更新现有的元素。
    Flutter中常见的Key有:

    - LocalKey
      - ObjectKey
      - UniqueKey
      - ValueKey
        - PageStorageKey
    - GlobalKey
      - GlobalObjectKey
      - LabeledGlobalKey
    

    3.2 何时需要使用Key?

    大多数时候不需要,不过当需要在一个相同类型的、有状态的widget集合中添加、删除或调整顺序时,就需要使用Key了。比如,在一个待办事项App中,我们需要可以执行添加新事项、根据优先级调整事项顺序、在完成事项后移除它们等操作。

    先通过一个简单的例子来了解一下为什么需要使用Key。下面是一个随机数列表:

    在这里插入图片描述

    RandomNum是一个StatelessWidget:

    class RandomNum extends StatelessWidget {
      final int num;
      RandomNum(): num = Random().nextInt(1000 * 1000), super();
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: EdgeInsets.all(8),
          child: Text('$num'),
        );
      }
    }
    

    当我们点击Reorder按钮时,随机数列表会重新排序,一切正常。然后我们将RandomNum改为StatefulWidget。

    class RandomNum extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => RandomNumState();
    }
    
    class RandomNumState extends State<RandomNum> {
      int num;
    
      @override
      void initState() {
        num = Random().nextInt(1000 * 1000);
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: EdgeInsets.all(8),
          child: Text('$num'),
        );
      }
    }
    

    此时,再点击 Reorder 按钮,屏幕上的数字没有变化。怎么回事呢?

    我们知道,在 Flutter 中,每个 Widget 都对应一个 Element。Element 树其实是非常简单的,仅保存了关联的 widget 的类型以及指向子 element 的连接。可以将 element 树看作 app 的骨架,它展示了app的结构,但是所有的附加信息需要通过指向widget的引用来查找。

    在这里插入图片描述

    这是无状态版本的 widget 和 element 树。当我们改变 items 的顺序时,Flutter会遍历 element 树以确认结构是否改变,从 Column 对应的 element 开始,一直到所有的子孙结点。对于每个 element,Flutter 会检查新 widget 的类型和 key 与当前引用的 widget 的类型和 key 是否一致,如果一致就将引用指向新的 widget。对于RandomNum 来说,由于没有 key ,因此 Flutter 只会检查其类型。


    当我们改变 items 的顺序后,widget 树会重建,但由于结构与之前一致,所以 element 树结构并不会改变,只不过 element 指向的 widget 引用发生了变化。对于 StatelessWidget 来说,这并没有什么问题,所有的信息都保存在 widget 中,只要改变 widget 树就可以了。

    在这里插入图片描述


    当 RandomNum 变为 StatefulWidget 后,widget 树、element 树与之前一样,但是增加了关联的 State 对象。此时,num 不再保存在 widget 中,而是保存在 State 对象中。当我们调整 widget 的顺序后,Flutter 依然会遍历 element 树,检查树结构是否改变,并更新指向 widget 的引用。

    Flutter根据element树以及关联的State对象来确定屏幕上的实际显示内容。因此,屏幕显示内容不会改变。现在,我们给RandomNum增加一个Key:

    class _RandomNumAppState extends State<RandomNumApp> {
      List<RandomNum> items;
      
      @override
      void initState() {
        items = [
          RandomNum(key: UniqueKey()),
          RandomNum(key: UniqueKey()),
          RandomNum(key: UniqueKey()),
          RandomNum(key: UniqueKey()),
          RandomNum(key: UniqueKey()),
          RandomNum(key: UniqueKey()),
        ];
        super.initState();
      }
      ...
    }
    
    class RandomNum extends StatefulWidget {
      RandomNum({Key key}): super(key: key);
    
      @override
      State<StatefulWidget> createState() => RandomNumState();
    }
    

    在这里插入图片描述


    当我们改变 items 的顺序时,Flutter 遍历 element 树以检查是否需要更新。Column 与之前一样,直接将 widget 引用指向新的 widget 即可。对于 RandomNum 的 element 来说,由于 element 的 Key 值与对应 widget 的 Key 值不同,因此 Flutter 会使这个 element 暂时失效,并移除对它的引用以及其对 widget 的引用。
    在这里插入图片描述


    然后,从第一个不匹配的widget开始,Flutter会在所有失效的子结点中查找具有对应Key值的element,如果找到了,则将这个element的widget引用指向到这个widget。接着对第二个widget做相同的操作,以此类推。

    在这里插入图片描述


    当遍历结束之后,更新element树的引用,此时widget、element、state就对应起来了。

    在这里插入图片描述


    总结来说,当我们需要更新一个有状态的、同类型的widget组成的集合时需要使用Key来保留widget的状态。

    3.3 应该在何处使用Key?

    当我们需要使用Key时,应该将Key用在widget树的什么位置呢?**需要保存状态的widget子树的顶层。**我们刚才一直在谈论state,你或许会认为应该用在第一个StatefulWidget上,不过这是错误的!!

    将上面的例子略作修改,我们将RandomNum这个StatefulWidget包裹在一个Padding里面:

    class _RandomNumAppState extends State<RandomNumApp> {
      List<Padding> items;
    
      @override
      void initState() {
        items = [
          Padding(
            padding: EdgeInsets.all(4),
            child: RandomNum(key: UniqueKey()),
          ),
          Padding(
            padding: EdgeInsets.all(4),
            child: RandomNum(key: UniqueKey()),
          ),
          Padding(
            padding: EdgeInsets.all(4),
            child: RandomNum(key: UniqueKey()),
          ),
          Padding(
            padding: EdgeInsets.all(4),
            child: RandomNum(key: UniqueKey()),
          ),
        ];
        super.initState();
      }
    }
    

    再次运行程序,我们发现所有的数字每次都重新生成了一遍,怎么回事呢?先来看一下添加Padding之后的widget和element树。
    在这里插入图片描述


    当我们改变RandomNum结点的位置时,Flutter的widget-to-element匹配算法每次在树中查找一级。我们先看第一层,即Padding层,暂时忽略其它结点,每次只看一层。

    在这里插入图片描述


    可以看到,对于Padding这一层来说,调整RandomNum的顺序之后,匹配关系并没有发生什么变化,Padding还没有Key,因此只需要比较其类型,显然类型都一样,更新element指向widget的引用即可。
    然后来看第二层,即第一个Padding对应的子树:

    在这里插入图片描述


    Element的key值与widget的key值不匹配,因此Flutter会使这个element失效并移除对它的连接。我们在例子中使用的是本地Key,这意味着Flutter只会在一个层级中使用这个Key值来匹配widget和element。由于无法在同级找到拥有相同Key值的element,因此Flutter会重新创建一个新的element并赋予新的State对象。所以,我们看到所有的数字都重新创建了。

    如果我们将Key用在Padding上,Flutter就会感知到变化并正确地更新连接,就像上面的例子一样。

    class _RandomNumAppState extends State<RandomNumApp> {
      List<Padding> items;
    
      @override
      void initState() {
        items = [
          Padding(
            key: UniqueKey(),
            padding: EdgeInsets.all(4),
            child: RandomNum(),
          ),
          Padding(
            key: UniqueKey(),
            padding: EdgeInsets.all(4),
            child: RandomNum(),
          ),
          Padding(
            key: UniqueKey(),
            padding: EdgeInsets.all(4),
            child: RandomNum(),
          ),
          Padding(
            key: UniqueKey(),
            padding: EdgeInsets.all(4),
            child: RandomNum(),
          ),
        ];
        super.initState();
      }
      ...
    }
    

    在这里插入图片描述


    3.4 应该使用什么类型的Key?

    我们已经知道何时需要使用Key,以及应该在何处使用Key。不过,如果我们看一下Flutter的文档,就会发现有很多类型的Key,那么应该使用什么类型的Key呢?

    当我们修改一个widget集合时,就像上面的将一组数字重新排序那样,只需要与其它widget的key区分开来即可。对于这种情况,可以根据widget中保存的信息来做选择。

    • LocalKey

    当我们修改一个widget集合时,就像上面的将一组数字重新排序那样,只需要与其它widget的key区分开来即可。对于这种情况,可以根据widget中保存的信息来做选择。

    • ValueKey

    相等性由其value属性确定。
    在一个待办事项列表中,如果每个列表项的文字是唯一的,那么ValueKey是不错的选择,将文字作为其值:

    return TodoItem(
      key: ValueKey(todo.task),
      todo: todo,
      onDismissed: (direction) {
        _removeTodo(context, todo);
      },
    );
    
    • ObjectKey

    相等性由其Object类似的value属性确定。
    如果widget中保存的是复杂信息的组合呢?比如在一个通讯录App中,每个人的信息有很多项:

    AddressBookEntry:
      FirstName: Hob
      LastName: Reload
      Birthday: July 18
      
    AddressBookEntry:
      FirstName: Ella
      LastName: Mentary
      Birthday: July 18
      
    AddressBookEntry:
      FirstName: Hob
      LastName: Thyme
      Birthday: February 29
    

    任何一项信息可能都不是唯一的,姓名、出生日期都可能重复,不过信息的组合是唯一的。对于这种情况,ObjectKey或许是最合适的。

    • UniqueKey

    只与自己相等。
    如果多个widget的值相同,或者想要确保每个Key的唯一性,可以使用UniqueKey。在上面的例子中使用的就是UniqueKey,因为我们没有在widget中保存任何不变且唯一的数据,数字需要等到RandomNum构建或initState时才能确定。

    • PageStorageKey

    定义PageStorage的值存放在何处的ValueKey。
    滚动列表(ScrollPosition)使用PageStorage保存滚动位置,每次滚动停止时都会更新PageStorage中保存的值。

    void didEndScroll() {
      activity.dispatchScrollEndNotification(copyWith(), context.notificationContext);
      if (keepScrollOffset)
        saveScrollOffset();
    }
    
    @protected
    void saveScrollOffset(){
      PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels);
    }
    

    PageStorage用于保存和恢复比widget生命周期长的数据,这些数据保存在一个per-route的Map中,Key由widget的PageStorageKey和其祖先结点来决定。要使widget重建时能够找到保存的值,key值的identity在每次widget构建时必须保持不变。

    例如,为了确保TabBarView重建时每个MyScrollableTabView的滚动位置都能被恢复,我们为其指定了PageStorageKey:

    TabBarView(
      children: myTabs.map((Tab tab) {
        MyScrollableTabView(
          key: PageStorageKey<String>(tab.text), // like 'Tab 1'
          tab: tab,
        ),
      }),
    )
    
    • GlobalKey

    在整个App中唯一的Key。
    GlobalKey唯一标识了一个element,通过GlobalKey可以访问与此element关联的其它对象,比如BuildContext。对于StatefulWidget来说,可以通过GlobalKey访问其State。
    与上面介绍的LocalKey不同,含有GlobalKey的widget在移动位置时可以改变父结点。与LocalKey相同的是,位置的移动必须在一个动画帧内完成。
    例如,我们想在不同的页面展示同一个widget,同时保持其状态,就需要使用GlobalKey。
    GlobalKey的成本比较高,如果不是为了上面的两个目的,即在保持widget状态的情况下更换父节点,或者需要访问在widget树中完全不同部分的widget中的信息,可以考虑使用上面介绍的LocalKey。

    当在widget树中更换位置时,使用Key来保持状态。最常见的场景是修改一个相同类型widget组成的集合,例如一个列表。将Key放在希望保持其状态的widget子树的顶部,根据widget中保存的信息类型选择合适的Key。



    参考资料

    1. Widget简介
    2. Widget基础系列 - Key
    展开全文
  • Widget 简介

    千次阅读 2021-11-05 16:57:05
    文章目录一、默认计数器应用 一、默认计数器应用 import 'package:flutter/material.dart'; //导包 void main() { runApp(const MyApp()); //应用入口 } /// 代表 Flutter 应用 ... Widget build(BuildCon

    一、默认计数器应用

    计数器执行流程:当右下角的floatingActionButton按钮被点击之后,会调用_incrementCounter方法。在_incrementCounter方法中,首先会自增_counter计数器(状态),然后setState会通知 Flutter 框架状态发生变化,接着,Flutter 框架会调用build方法以新的状态重新构建UI,最终显示在设备屏幕上。

    Flutter 中是通过 Widget 嵌套 Widget 的方式来构建UI和进行实践处理的,所以记住,Flutter 中万物皆为Widget。

    import 'package:flutter/material.dart'; //导包
    
    void main() {
      runApp(const MyApp()); //应用入口
    }
    
    /// 代表 Flutter 应用
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          //应用名称
          title: 'Flutter Demo',
          theme: ThemeData(
            //蓝色主题
            primarySwatch: Colors.blue,
          ),
          //应用首页路由
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    /// 应用的首页
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    /// MyHomePage类对应的状态类
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      //当按钮点击时,会调用此函数,该函数的作用是先自增_counter,然后调用setState 方法。
      // setState方法的作用是通知 Flutter 框架,有状态发生了改变,
      // Flutter 框架收到通知后,会执行 build 方法来根据新的状态重新构建界面
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      // 构建UI界面的逻辑
      @override
      Widget build(BuildContext context) {
        // 1、Scaffold 是 Material 库中提供的页面脚手架,它提供了默认的导航栏、标题和包含主屏幕 widget 树(后同“组件树”或“部件树”)的body属性,组件树可以很复杂。
        // 2、body的组件树中包含了一个Center 组件,Center 可以将其子组件树对齐到屏幕中心。
        // 3、
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            // Center 子组件是一个Column 组件,Column的作用是将其所有子组件沿屏幕垂直方向依次排列;
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          // floatingActionButton是页面右下角的带“+”的悬浮按钮,它的onPressed属性接受一个回调函数,代表它被点击后的处理器,
          // 本例中直接将_incrementCounter方法作为其处理函数。
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

    二、Widget 接口

    Widget 的部分源码。

    @immutable // 不可变的
    abstract class Widget extends DiagnosticableTree {
      const Widget({ this.key });
    
      final Key? key;
    
      @protected
      @factory
      Element createElement();
    
      @override
      String toStringShort() {
        final String type = objectRuntimeType(this, 'Widget');
        return key == null ? type : '$type-$key';
      }
    
      @override
      void debugFillProperties(DiagnosticPropertiesBuilder properties) {
        super.debugFillProperties(properties);
        properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
      }
    
      @override
      @nonVirtual
      bool operator ==(Object other) => super == other;
    
      @override
      @nonVirtual
      int get hashCode => super.hashCode;
    
      static bool canUpdate(Widget oldWidget, Widget newWidget) {
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
      ...
    }
    
    • @immutable 代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final),为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。这也是为什么 Widget 中定义的属性必须是 final 的原因。
    • Widget 类继承自 DiagnosticableTree,DiagnosticableTree 即“诊断树”,主要作用是提供调试信息。
    • Key: 这个 key 属性的主要的作用是决定是否在下一次 build时 复用旧的 Widget ,决定的条件在canUpdate() 方法中。
    • createElement():“一个 Widget 可以对应多个 Element”;Flutter 框架在构建 UI 树时,会先调用此方法生成对应节点的 Element 对象。此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。
    • debugFillProperties(…) 复写父类的方法,主要是设置诊断树的一些特性。
    • canUpdate(…) 是一个静态方法,它主要用于在 Widget 树重新 build 时复用旧的 widget ,其实具体来说,应该是:是否用新的 Widget 对象去更新旧 UI 树上所对应的 Element 对象的配置;通过其源码我们可以看到,只要 newWidget 与 oldWidget 的 runtimeType 和 key 同时相等时就会用 newWidget 去更新Element 对象的配置,否则就会创建新的 Element。

    三、三棵树

    Flutter 中有三棵树:Widget 树,Element 树和 RenderObject 树。当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,同时与 Widget Tree 相对应,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。 Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如 MultiChildRenderObjectElement。最终所有 Elemen t的RenderObject 构成一棵树,我们称之为”Render Tree“即”渲染树“。总结一下,我们可以认为 Flutter 的UI 系统包含三棵树:Widget 树、Element 树、渲染树。他们的依赖关系是:根据 Widget 树生成Element 树,再依赖于 Element 树生成 RenderObject 树。
    在这里插入图片描述
    在 flutter 中,Container、Text 等组件都属于 Widget,所以这课树就是 Widget 树,也可以叫做控件树,它就表示了我们在 dart 代码中所写的控件的结构。Element 就是 Widget 的另一种抽象。我们在代码中使用的像 Container、Text 等这类组件和其属性只不过是我们想要构建的组件的配置信息,当我们第一次调用 build()`方法想要在屏幕上显示这些组件时,Flutter 会根据这些信息生成该 Widget 控件对应的 Element,同样地,Element 也会被放到相应的 Element 树当中。RenderObject 在 Flutter 当中做组件布局渲染的工作,其为了组件间的渲染搭配及布局约束也有对应的 RenderObject 树,我们也称之为渲染树。

    作者:邱穆
    链接:https://www.jianshu.com/p/e2c2ea310bdc

    四、StatelessWidget 和 StatefulWidget

    在 Flutter 中,Widget 分为两类:Stateless(无状态)和 Stateful(有状态)Widget。StatelessWidget 没有内部状态,Icon、IconButton, 和 Text 都是无状态 Widget, 它们都是 StatelessWidget 的子类。StatefulWidget 是动态的,用户可以和其交互(例如输入一个表单、 或者移动一个 slider 滑块),或者可以随时间改变 (也许是数据改变导致的 UI 更新)。Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 Stateless widgets, 它们都是 StatefulWidget 的子类。

    StatelessWidget
    StatelessWidget 是不可变的, 这意味着它们的属性不能改变——所有的值都是最终的。如果无状态Widget 里面有子 Widget,并且子 Widget 是有状态的,则子 Widget 的内容是可以通过 setState 来更改的。无状态 Widget 影响的仅仅是自己是无状态的,不会影响他的父 Widget 和子 Widget。

    StatefulWidget
    StatefulWidget 持有的状态可能在 Widget 生命周期中发生变化。

    State
    一个 StatefulWidget 类会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息可以:

    • 在 Widget 构建时可以被同步读取。
    • 在 Widget 生命周期中可以被改变,当 State 被改变时,可以手动调用其 setState() 方法通知 Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其 build 方法重新构建 Widget 树,从而达到更新 UI 的目的。

    State 中有两个常用属性:

    • widget ,它表示与该 State 实例关联的 Widget 实例,由 Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI 树上的某一个节点的 Widget 实例在重新构建时可能会变化,但State 实例只会在第一次插入到树中时被创建,当在重新构建时,如果 Widget 被修改了,Flutter 框架会动态设置State,widget 为新的 Widget 实例。
    • context。StatefulWidget 对应的 BuildContext,作用同 StatelessWidget 的 BuildContext。

    五、Widget 生命周期

    Widget 生命周期主要分为 StatefullWidget 的生命周期和 StatelessWidget 的生命周期,StatelessWidget 的生命周期比较简单,它只有一个 build 方法,我们在这儿不做过多关注,以下主要分析 StatefullWidget 的生命周期。

    仍然以计数器功能为例,实现一个计数器 CounterWidget 组件 ,点击它可以使计数器加1,由于要保存计数器的数值状态,所以我们应继承 StatefulWidget。

    import 'package:flutter/material.dart'; //导包
    
    void main() {
      runApp(const MyApp()); //应用入口
    }
    
    /// 代表 Flutter 应用
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: StateLifecycleTest(),
        );
      }
    }
    
    /// 路由
    class StateLifecycleTest extends StatelessWidget {
      const StateLifecycleTest({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const CounterWidget();
      }
    }
    
    /// 计数器组件
    class CounterWidget extends StatefulWidget {
      const CounterWidget({Key? key, this.initValue = 0});
    
      final int initValue;
    
      @override
      _CounterWidgetState createState() => _CounterWidgetState();
    }
    
    /// State 代码
    class _CounterWidgetState extends State<CounterWidget> {
      int _counter = 0;
    
      @override
      void initState() {
        super.initState();
        //初始化状态
        _counter = widget.initValue;
        print("initState");
      }
    
      @override
      Widget build(BuildContext context) {
        print("build");
        return Scaffold(
          body: Center(
            child: TextButton(
              child: Text('$_counter'),
              //点击后计数器自增
              onPressed: () => setState(
                () => ++_counter,
              ),
            ),
          ),
        );
      }
    
      @override
      void didUpdateWidget(CounterWidget oldWidget) {
        super.didUpdateWidget(oldWidget);
        print("didUpdateWidget ");
      }
    
      @override
      void deactivate() {
        super.deactivate();
        print("deactivate");
      }
    
      @override
      void dispose() {
        super.dispose();
        print("dispose");
      }
    
      @override
      void reassemble() {
        super.reassemble();
        print("reassemble");
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print("didChangeDependencies");
      }
    }
    

    监听 App 生命周期

    1、组合 WidgetsBindingObserver 类。

    class _CounterWidgetState extends State<CounterWidget> with WidgetsBindingObserver
    

    2、在 initState 中添加监听。

    WidgetsBinding.instance.addObserver(this);
    

    3、在 dispose 中添加监听。

    WidgetsBinding.instance.removeObserver(this);
    

    4、State 中实现状态监听。

    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
      super.didChangeAppLifecycleState(state);
      if (state == AppLifecycleState.paused) {
        // The application is not currently visible to the user, not responding to
        // user input, and running in the background.
        // 不可见,不可操作
      }
      if (state == AppLifecycleState.resumed) {
        // The application is visible and responding to user input.
        // 可见,可操作
      }
      if (state == AppLifecycleState.inactive) {
        // The application is in an inactive state and is not receiving user input.
        // 可见,不可操作
      }
      if (state == AppLifecycleState.detached) {
        // The application is still hosted on a flutter engine but is detached from any host views.
        // 虽然还在运行,但已经没有任何存在的界面。
      }
    }
    

    使用场景:
    场景 1:前台转后台:

    • AppLifecycleState.inactive
    • AppLifecycleState.paused

    场景 2:后台转前台:

    • AppLifecycleState.inactive
    • AppLifecycleState.resumed

    生命周期方法说明

    1. constructor:构造函数
    2. createState:createState 是 StatefulWidget 里创建 State 的方法,当要创建新的 StatefulWidget 的时候,会立即执行 createState,而且只执行一次。
    3. initState:这个方法是组件创建后的第一个方法,它相当于原生的 onCreate 和 viewDidLoad,大部分页面初始逻辑调用会写到这个方法中。
    4. didChangeDependencies:当 State 对象的依赖发生变化时会被调用。例:你的 StatefulWidget 依赖一个 InheritedWidget 的数据,当数据发生变化时,会调用子 Widget 的该方法。第一次打开页面时 initState 之后会立刻调用该方法。
    5. didUpdateWidget:当父组件改变并且需要重新绘制 UI 时,这个方法会调用。通过这个方法,你可以通过参数获取到旧的组件,你可以通过对比新旧组件后做一些额外的逻辑。
    6. build:这个方法是最为重要的一个方法,它用来构建你的整个组件树。
    7. deactivate:当 State 对象从树中被移除时,会调用此回调。有些情况下,framework 会将该 State 插入到树的其它部分。
    8. dispose:当 State 对象从树中被永久移除时调用。通常在此回调中释放资源。
    9. reassemble:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release 模式下永远不会被调用。

    使用场景
    场景 1:打开页面:

    1. constructor
    2. createState
    3. initState
    4. didChangeDependencies
    5. build

    场景 2:退出页面:

    1. deactivate
    2. dispose

    场景 3:热重载:

    1. reassemble
    2. didUpdateWidget
    3. build

    场景 4:横竖屏切换:

    1. didUpdateWidget
    2. build
    3. didUpdateWidget
    4. build

    ————————————————
    原文链接:https://blog.csdn.net/haha223545/article/details/105483176

    六、在 widget 树中获取 State 对象

    有两种方法在子 widget 树中获取父级 StatefulWidget 的State 对象。

    通过 Context 获取
    一般来说,如果 StatefulWidget 的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其 State 对象;如果 StatefulWidget 的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其 State 对象。但是通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个 of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供 of 方法。这个约定在 Flutter SDK 里随处可见。

    import 'package:flutter/material.dart'; //导包
    
    void main() {
      runApp(const MyApp()); //应用入口
    }
    
    /// 代表 Flutter 应用
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          //应用首页路由
          home: GetStateObjectRoute(),
        );
      }
    }
    
    class GetStateObjectRoute extends StatefulWidget {
      const GetStateObjectRoute({Key? key}) : super(key: key);
    
      @override
      State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
    }
    
    class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("子树中获取State对象"),
          ),
          body: Center(
            child: Column(
              children: [
                Builder(builder: (context) {
                  return ElevatedButton(
                    onPressed: () {
                      // 查找父级最近的Scaffold对应的ScaffoldState对象
                      ScaffoldState _state =
                          context.findAncestorStateOfType<ScaffoldState>()!;
                      // 打开抽屉菜单
                      _state.openDrawer();
                    },
                    child: Text('打开抽屉菜单1'),
                  );
                }),
                Builder(builder: (context) {
                  return ElevatedButton(
                    onPressed: () {
                      // 直接通过of静态方法来获取ScaffoldState
                      ScaffoldState _state = Scaffold.of(context);
                      // 打开抽屉菜单
                      _state.openDrawer();
                    },
                    child: Text('打开抽屉菜单2'),
                  );
                }),
                Builder(builder: (context) {
                  return ElevatedButton(
                    onPressed: () {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text("我是SnackBar")),
                      );
                    },
                    child: Text('显示SnackBar'),
                  );
                }),
              ],
            ),
          ),
          drawer: Drawer(),
        );
      }
    }
    

    在这里插入图片描述
    通过 GlobalKey 获取
    GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过 globalKey.currentWidget 获得该 widget 对象、globalKey.currentElement 来获得 widget 对应的 element 对象,如果当前 widget 是 StatefulWidget,则可以通过 globalKey.currentState 来获得该 widget 对应的state对象。

    注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。

    import 'package:flutter/material.dart'; //导包
    
    void main() {
      runApp(const MyApp()); //应用入口
    }
    
    /// 代表 Flutter 应用
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          //应用首页路由
          home: GetStateObjectRoute(),
        );
      }
    }
    
    class GetStateObjectRoute extends StatefulWidget {
      const GetStateObjectRoute({Key? key}) : super(key: key);
    
      @override
      State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
    }
    
    class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
      //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
      static GlobalKey<ScaffoldState> _globalKey = GlobalKey();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          key: _globalKey, //设置key
          appBar: AppBar(
            title: Text("子树中获取State对象"),
          ),
          body: Center(
            child: Column(
              children: [
                Builder(builder: (context) {
                  return ElevatedButton(
                    onPressed: () {
                      //通过GlobalKey来获取State对象
                      _globalKey.currentState?.openDrawer();
                    },
                    child: Text('打开抽屉菜单1'),
                  );
                }),
              ],
            ),
          ),
          drawer: Drawer(),
        );
      }
    }
    

    在这里插入图片描述

    七、通过 RenderObject 自定义 Widget

    StatelessWidget 和 StatefulWidget 都是用于组合其它组件的,它们本身没有对应的 RenderObject。Flutter 组件库中的很多基础组件都不是通过 StatelessWidget 和 StatefulWidget 来实现的,比如 Text 、Column、Align 等。

    如果组件不会包含子组件,则我们可以直接继承自 LeafRenderObjectWidget ,它是 RenderObjectWidget 的子类,而 RenderObjectWidget 继承自 Widget 。

    class CustomWidget extends LeafRenderObjectWidget{
      @override
      RenderObject createRenderObject(BuildContext context) {
        // 创建 RenderObject
        return RenderCustomObject();
      }
      @override
      void updateRenderObject(BuildContext context, RenderCustomObject  renderObject) {
        // 更新 RenderObject
        super.updateRenderObject(context, renderObject);
      }
    }
    
    class RenderCustomObject extends RenderBox{
    
      @override
      void performLayout() {
        // 实现布局逻辑
      }
    
      @override
      void paint(PaintingContext context, Offset offset) {
        // 实现绘制
      }
    }
    

    如果自定义的 widget 可以包含子组件,则可以根据子组件的数量来选择继承SingleChildRenderObjectWidget 或 MultiChildRenderObjectWidget。通常,我们的 Widget 可以继承自以下三种类

    • SingleChildRenderObjectWidget:RenderObject只有一个 child。
    • MultiChildRenderObjectWidget:可以有多个 child。
    • LeafRenderObjectWidget :RenderObject 是一个叶子节点,没有 child。
      ————————————————
      原文链接:https://blog.csdn.net/yingshukun/article/details/107814111

    八、Flutter SDK 内置组件库介绍

    Flutter 提供了一套丰富、强大的基础组件,在基础组件库之上 Flutter 又提供了一套 Material 风格( Android 默认的视觉风格)和一套 Cupertino 风格(iOS视觉风格)的组件库。要使用基础组件库,需要先导入:

    import 'package:flutter/widgets.dart';
    

    基础组件

    • Text (opens new window):创建一个带格式的文本。
    • Row (opens new window)、 Column (opens new window): 这些具有弹性空间的布局类 widget 可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。
    • Stack (opens new window): 取代线性布局 (和 Android 中的 FrameLayout 相似),Stack 允许子 widget 堆叠, 你可以使用 Positioned (opens new window)来定位他们相对于 Stack 的上下左右四条边的位置。
    • Container (opens new window): Container (opens new window)可让您创建矩形视觉元素。Container 可以装饰一个 BoxDecoration (opens new window), 如 background、一个边框、或者一个阴影。 Container (opens new window)也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container (opens new window)可以使用矩阵在三维空间中对其进行变换。

    Material 组件
    Flutter 提供了一套丰富 的 Material 组件,它可以帮助我们构建遵循 Material Design 设计规范的应用程序。Material 应用程序以 MaterialApp (opens new window) 组件开始, 该组件在应用程序的根部创建了一些必要的组件,比如 Theme 组件,它用于配置应用的主题。 是否使用 MaterialApp (opens new window)完全是可选的,但是使用它是一个很好的做法。要使用 Material 组件,需要先引入它:

    import 'package:flutter/material.dart';
    

    Cupertino 组件
    Flutter 也提供了一套丰富的 Cupertino 风格的组件,尽管目前还没有 Material 组件那么丰富,但是它仍在不断的完善中。值得一提的是在 Material 组件库中有一些组件可以根据实际运行平台来切换表现风格,比如 MaterialPageRoute,在路由切换时,如果是 Android 系统,它将会使用 Android 系统默认的页面切换动画(从底向上);如果是 iOS 系统,它会使用 iOS 系统默认的页面切换动画(从右向左)。

    展开全文
  • dcharts-widget

    2016-06-21 11:41:34
    dcharts-widget
  • Qt StackWidget切换动画

    2016-07-15 16:53:44
    QStackedWidget切换widget时的动画
  • ShadowWidget.rar

    2019-08-18 18:33:28
    Qt自定义外发光效果,相当于CSS中的box-shadow样式,以及自定义边框阴影效果。
  • Widget原理分析

    千次阅读 2021-12-15 19:29:13
    01.Widget基础概念 1.1 Widget概念 1.2 Widget骨架 1.3 Widget源码 1.4 Widget不可变 02.StatelessWidget源码 03.StatefulWidget源码 04.InheritedWidget源码 05.Context是什么作用 01.Widget基础概念 1.1 ...

    目录介绍

    • 01.Widget基础概念
      • 1.1 Widget概念
      • 1.2 Widget骨架
      • 1.3 Widget源码
      • 1.4 Widget不可变
    • 02.StatelessWidget源码
    • 03.StatefulWidget源码
    • 04.InheritedWidget源码
    • 05.Context是什么作用

    01.Widget基础概念

    1.1 Widget概念

    • 在Flutter中几乎所有的对象都是一个Widget。
      • 与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,而原生开发中的控件通常只是指UI元素。
      • 在描述UI元素时可能会用到“控件”、“组件”这样的概念,读者心里需要知道他们就是widget,只是在不同场景的不同表述而已。
      • 由于Flutter主要就是用于构建用户界面的,所以,在大多数时候,可以认为widget就是一个控件,不必纠结于概念。

    1.2 Widget骨架

    • Widget 的骨架
      • 常用的 StatefulWidget、StatelessWidget,再加上 (InheritedWidget) 或 ProxyWidget 和 RenderObjectWidget 都继承于 Widget 基类,他们整体构成了 Widget 的骨架。
    • 有状态 和 无状态
      • StatelessWidget 无状态的 Widget,常见的子类如 Text、Container。
      • StatefulWidget 有状态的 Widget,常用的子类有 Image、Navigator。
      • ProxyWidget 为代理 Widget,可以快速追溯父节点,通常用来做数据共享,常见的子类 InheritedWidget,各种状态管理框架,如 provider 等正是基于它实现。
      • 什么叫做“状态”?Widget 在 Flutter 架构下设计为不可变的,通常情况下每一帧都会重新构建一个新的 Widget 对象,而无法知道之前的状态。StatefulWidget 通过关联一个 State 对象实现状态的保存。

    1.3 Widget源码

    • Widget源码如下所示
      abstract class Widget extends DiagnosticableTree {
        const Widget({ this.key });
      
        final Key key;
      
        @protected
        @factory
        Element createElement();
      
        @override
        String toStringShort() {
          final String type = objectRuntimeType(this, 'Widget');
          return key == null ? type : '$type-$key';
        }
      
        @override
        void debugFillProperties(DiagnosticPropertiesBuilder properties) {
          super.debugFillProperties(properties);
          properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
        }
      
        @override
        @nonVirtual
        bool operator ==(Object other) => super == other;
      
        @override
        @nonVirtual
        int get hashCode => super.hashCode;
      
        static bool canUpdate(Widget oldWidget, Widget newWidget) {
          return oldWidget.runtimeType == newWidget.runtimeType
              && oldWidget.key == newWidget.key;
        }
      
        static int _debugConcreteSubtype(Widget widget) {
          return widget is StatefulWidget ? 1 :
                 widget is StatelessWidget ? 2 :
                 0;
          }
      }
      
    • 主要方法和属性介绍
      • Widget类继承自DiagnosticableTreeDiagnosticableTree即“诊断树”,主要作用是提供调试信息。
      • Key: 这个key属性类似于React/Vue中的key,主要的作用是决定是否在下一次build时复用旧的widget,决定的条件在canUpdate()方法中。
      • createElement():正如所述“一个Widget可以对应一个Element”;Flutter Framework在构建UI树时,会先调用此方法生成对应节点的Element对象。此方法是Flutter Framework隐式调用的,在我们开发过程中基本不会调用到。
      • debugFillProperties(...) 复写父类的方法,主要是设置诊断树的一些特性。
      • canUpdate(...)是一个静态方法,它主要用于在Widget树重新build时复用旧的widget,其实具体来说,应该是:是否用新的Widget对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidgetoldWidgetruntimeTypekey同时相等时就会用newWidget去更新Element对象的配置,否则就会创建新的Element
    • 核心方法createElement()
      • Widget类本身是一个抽象类,其中最核心的就是定义了createElement()接口。
      • 在Flutter开发中,我们一般都不用直接继承Widget类来实现一个新组件,相反,我们通常会通过继承StatelessWidgetStatefulWidget来间接继承Widget类来实现。
      • StatelessWidgetStatefulWidget都是直接继承自Widget类,而这两个类也正是Flutter中非常重要的两个抽象类,它们引入了两种Widget模型。
    • 核心方法canUpdate()
      • 实际上就是比较两个 widget 的 runtimeType 和 key 是否相同。
        • runtimeType 也就是类型,如果新老 widget 的类型都变了,显然需要重新创建 Element。
        • key Flutter 中另一个核心的概念,key 的存在影响了 widget 的更新、复用流程,这里先不展开。
      • 默认情况下 widget 创建时不需传入 key,因此更多情况下只需要比较二者的类型,如果类型一样,那么当前节点的 Element 不需要重建,接下来继续调用 child.update 更新子树。

    1.4 Widget不可变

    • Widget 是一个很重要的概念,但是Widget有一个更重重要的特性,就是Widget是immutable(不可变的),这是什么意思?
      • 拿 Opacity 为例给讲解,讲解之前先看一下Opacity的继承关系。(在讲源码之前我们先看一下Opacity的职责是什么,Opacity是一个能让他的孩子透明的组件,很简单也很容易理解。)
      • Opacity继承自SingleChildRenderObjectWidget,这类只包含了一个child的Widget,它继承自RenderObjectWidget,RenderObjectWidget继承自Widget。
      class Opacity extends SingleChildRenderObjectWidget {
      }
      
      abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
      }
      
      abstract class RenderObjectWidget extends Widget {
      }
      
    • 然后看一下 Opacity 简单源码
      class Opacity extends SingleChildRenderObjectWidget {
        
        const Opacity({
          Key key,
          @required this.opacity,
          Widget child,
        }) : super(key: key, child: child);
        
        final double opacity;//注释1
        
        @override
        RenderOpacity createRenderObject(BuildContext context) {//注释2
          return RenderOpacity(
            opacity: opacity
          );
        }
      
        @override
        void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
          renderObject
            ..opacity = opacity
        }
      }
      
      • 在注释1处声明了一个属性,这属性是final,也就除了构造函数能给这个属性赋值之外,没有其他的办法让这个值进行改变。那我们想改变这个值怎么办,唯一的办法就是创建一个新的Opacity。

    02.StatelessWidget源码

    • StatelessWidget源码如下所示
      abstract class StatelessWidget extends Widget {
        const StatelessWidget({ Key key }) : super(key: key);
      
        @override
        StatelessElement createElement() => StatelessElement(this);
      
        @protected
        Widget build(BuildContext context);
      }
      
    • StatelessWidget相对比较简单,它继承自Widget类,重写了createElement()方法。
      • StatelessElement 间接继承自Element类,与StatelessWidget相对应(作为其配置数据)。
    • StatelessWidget用于不需要维护状态的场景。
      • 它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。

    03.StatefulWidget源码

    • StatefulWidget源码如下所示
      abstract class StatefulWidget extends Widget {
        const StatefulWidget({ Key key }) : super(key: key);
      
        @override
        StatefulElement createElement() => StatefulElement(this);
      
        @protected
        @factory
        State createState();
      }
      
    • StatefulElement 间接继承自Element类,与StatefulWidget相对应(作为其配置数据)。
      • StatefulElement中可能会多次调用createState()来创建状态(State)对象。
    • createState() 用于创建和Stateful widget相关的状态,它在Stateful widget的生命周期中可能会被多次调用。
      • 例如,当一个Stateful widget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。
    • 理解树的概念
      • 在不同的场景可能指不同的意思,在说“widget树”时它可以指widget结构树,但由于widget与Element有对应关系(一可能对多)。
      • 在有些场景(Flutter的SDK文档中)也代指“UI树”的意思。
      • 而在stateful widget中,State对象也和StatefulElement具有对应关系(一对一),所以在Flutter的SDK文档中,可以经常看到“从树中移除State对象”或“插入State对象到树中”这样的描述。
      • 其实,无论哪种描述,其意思都是在描述“一棵构成用户界面的节点元素的树”,如果没有特别说明,都可抽象的认为它是“一棵构成用户界面的节点元素的树”。

    04.InheritedWidget源码

    4.1 InheritedWidget源码分析

    • InheritedWidget源码如下所示
      abstract class InheritedWidget extends ProxyWidget {
      
        const InheritedWidget({ Key key, Widget child })
          : super(key: key, child: child);
      
        @override
        InheritedElement createElement() => InheritedElement(this);
      
        @protected
        bool updateShouldNotify(covariant InheritedWidget oldWidget);
      }
      
    • 如果想自己实现一个类似主题变更后,更新相应 UI 的功能应该怎么做?
      • 大致思路应该就是一个观察者模式,凡是使用的主题数据的地方,需要向 主题中心 注册一个观察者,当主题数据发生 改变 时,主题中心依次通知各个观察者进行 UI 更新。
      • 这里有个问题需要解决,如何定义 数据改变 ?事实上,数据是否改变是由业务方决定的,因此这里需要抽象出相应接口,来看 InheritedWidget 结构。
    • 核心就是 updateShouldNotify 方法
      • 入参为原始的 widget,返回值为布尔值,业务方需要实现此方法,判断是否需要将变化通知到各个观察者。

    4.2 注册和通知流程

    • 举一个常见例子
      • 比如,app设置了theme主题,当修改了theme主题颜色,是怎么修改全局所有页面的theme状态的呢?这个就用到了注册和通知的功能,接着往下看:
    • 注册流程
      • 假设 StudyWidget 是我们业务侧的 Widget,其内部使用了 Theme.of(context) 方法获取任意主题信息后,会经一系列调用,最终将这个 context——StudyWidget 对应的 Element 对象,注册到 InheritedElement的成员变量 Map<Element, Object> _dependents 中。
      • 另外需要注意,之所以在第二步中,可以找到父 InheritedElement,是因为在 Element 的 mount 过程中,会将父 Widget 中保存的 Map<Type, InheritedElement> _inheritedWidgets 集合,依次传递给子 Widget。如果自身也是 InheritedElement 也会添加到这个集合中。
      • 当我们使用 MaterialApp 或 CupertinoApp 作为根节点时,其内部已经帮我们封装了一个 Theme Widget,因此不需要我们额外的套一层作为注册了。
    • 通知流程
      • 当父 InheritedWidget 发生状态改变时,最终会调用到 InheritedElement 的 update 方法,我们以此作为通知的起点。
      • 可以看到,流程最终会将依赖的 Element 标脏,在下一帧重绘时将会更新对应 Widget 的状态。至此,InheritedWidget 整体的注册、通知流程结束。

    05.Context是什么作用

    • 什么是Context
      • build方法有一个context参数,它是BuildContext类的一个实例,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。
      • 实际上,context是当前widget在widget树中位置中执行”相关操作“的一个句柄,比如它提供了从当前widget开始向上遍历widget树以及按照widget类型查找父级widget的方法。
    • 下面是在子树中获取父级widget的一个示例:
      class ContextRoute extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text("Context测试"),
            ),
            body: Container(
              child: Builder(builder: (context) {
                // 在Widget树中向上查找最近的父级`Scaffold` widget
                Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
                // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
                return (scaffold.appBar as AppBar).title;
              }),
            ),
          );
        }
      }
      

    推荐:https://github.com/yangchong211/YCFlutterUtils

    展开全文
  • Flutter Widget详解

    万次阅读 2020-09-24 19:39:05
    Flutter Widget详解 Widget是Flutter应用程序用户界面的基本构建块。每个Widget都是用户界面一部分的不可变声明。 与其他将视图、控制器、布局和其他属性分离的框架不同,Flutter具有一致的统一对象模型:widget。 ...
  • Widget之间通信平台的研究与应用,夏姣姣,,目前互联网的Widget主要包括以iGoogle为代表的Web Widget以及以Yahoo Widget为代表的桌面Widget,本文主要研究Web Widget。现有的主流Web Widget主要��
  • 关于iOS Widget(Locket Widget App)

    千次阅读 2022-04-28 18:12:07
    最近一款UI风格很像Clubhouse的App,功能简单到不能再简单的Locket app火了,其主要功能就是通过桌面Widget显示一张朋友分享的照片,死气沉沉的互联网上终于出现了一点新东西。其实widget的概念自从ios8(2014年)开始...
  • Flutter中的widget

    千次阅读 2021-12-14 23:03:52
    StatelessWidget和 StatefulWidget是 flutter的基础组件,日常开发中自定义 Widget都是选择继承这两者之一。也是在往后的开放中,我们最多接触的Widget: StatelessWidget:无状态的,展示信息,面向那些始终不变的...
  • 【Flutter核心类分析】深入理解Widget

    千次阅读 2021-11-16 20:35:39
    相信我们在Flutter开发过程中接触最多的无疑就是Widget了,通过Widget我们可以实现诸多功能: 描述UI的层级结构 定制UI的各色样式,国际化(font, color, theme) 定义UI布局方式(padding,center等) 数据共享...
  • android app widget demo

    2016-09-22 11:47:23
    1,widget 基本展示 2,widget点击跳转到activity 3,发送广播事件,更新widget界面
  • DockWidget_QT_DockWidget_

    2021-09-30 12:06:39
    Qt的DockWidget的使用,包括DockWidget的移动,布局等
  • 三种类型的Widget:Stateless Widget(无状态), Stateful Widget(有状态) 和Inherited Widget(继承型)
  • QT的Listwidget控件使用

    千次阅读 2021-03-23 15:24:09
    另一类是 Item Widgets,包括 QListWidget、QTreeWidget 和 QTable Widget。QListWidget其实是QListView的遍历类,QListView 是基于模型/视图(Model/View)结构,视图(View)与模型数据(Model Data)关联实现数据的...
  • jquery.ui.widget.js

    2014-12-05 11:45:21
    jquery的ui插件核心js文件: jquery.ui.widget.js
  • 当一个Widget状态发生变化时,Widget就会重新调用build()函数来返回控件的描述,过程中Flutter框架会与之前的Widget进行比较,确保实现渲染树中最小的变动来保证性能和稳定性。换句话说,当Widge
  • Android Widget详解(一)

    千次阅读 2021-06-10 08:22:32
    前言实习需要最近在研究Android的Widget,看了很多帖子个人觉得比较零散,特地在此总结一下,写的不好大家见谅哈^_^本博客全套源码稍后会提供下载什么是Widgetwidget是安卓较苹果特有的桌面小控件,Widget可以让我们...
  • WordPress Widget Boilerplate是使用诸如现代工具和面向对象的方法构建WordPress Widget的基础,中。 产品特点 Widget Boilerplate完全基于WordPress 使用约定可以轻松地遵循代码。 在将其提交到存储库之前,使用...
  • fscript 的 widget 扩展函数

    千次阅读 2021-01-10 09:17:20
    fscript 的 widget 扩展函数 1. 介绍 fscript 的 widget 扩展函数只能在 widget 的事件处理函数中使用,包括传统的 AWTK 应用程序和 AWTK-MVVM 应用程序的事件处理函数。 fscript 和其它主流脚本 (lua/js) 相比,...
  • 理论讲解了Widget的渲染过程实现原理
  • widget模式

    千次阅读 2021-01-17 13:40:10
    widget模式是一款非常有趣实用的桌面小组件收纳工具,这款软件拥有着多种多样的软件功能,还可以为大家设置各种自定义背景颜色设置还有组件样式调节,随着苹果手机iOS 14的更新,小组件功能也越发升级,能够在这里...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 572,508
精华内容 229,003
关键字:

Widget