widgets简介

Flutter widget创建于源自React的灵感的现代框架,其核心思想便是使用widgets来创建你的用户界面。widget描述了赋予配置和状态的它们应当展现的样子。当一个widget的状态发生改变,widget将根据其描述进行重构,框架将会与之前的描述进行对比,以便确认widget从一种状态转向另一种状态从底层渲染所需的最小更改。

注意: 如果你想要通过源代码来更深入地了解Flutter,可通过 基础布局指引布局创建,以及 为你的Flutter App添加交互 进行。

Hello world

一个widget仅调用了runApp()函数的最小的Flutter App

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

runApp函数接收给定的widget(在这里是Center)并作为wiget的根节点。在上面的示例中,widget树包含有两个widget,Center以及它的子级widget:Text。框架强制根级widget全屏,这意味着文本“Hello,world!”将位于屏幕的正中央。文本显示方向在这个实例中需要被指定;倘若使用的是MaterialAPP widget,那么文字方向将会默认为从左至右,稍后会再做演示。

当你需要编写一个app时,通常需要编写父类为StatelessWidget或StatefulWidget的widget,这取决与你的widget是否需要管理状态。一个widget的主要任务是要实现build()函数,使用基础widgets以描述自身的各个方面。框架将按次序构建widgets,直到渲染到基于RenderObject的最底部widget,它会计算并描述出widget的几何构成。

基础widgets(部件)

Flutter提供了如下一套通用的、强大的部件(widget):

Text

Text部件允许你在应用中创建带有样式的文字内容。

Row, Column

这两个widget使你可以创建水平以及垂直方向的弹性布局。这种设计是基于web技术下的flexbox布局模式。

Stack

用以替代线性方向(水平或垂直),Stack widget允许子级部件(widget)进行堆叠。你可以使用Positioned 来描述子级widget相对于Stack widget上下左右位置。Stack布局设计基于web技术的绝对定位布局模式。

Container

Container 部件使你可以创建一个矩形的可视化元素。一个Container能够使用BoxDecoration进行修饰,例如北京、边框、或阴影。Container同时还具有边距(margins)、填充(padding)以及大小约束。另外,Container还能够使用矩阵在三维空间进行转换。

以下是一些使用这些widgets和其他widgets的综合示例

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        // <Widget> is the type of items in the list.
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child to fill the available space.
          Expanded(
            child: title,
          ),
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece of paper on which the UI appears.
    return Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: <Widget>[
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.headline6,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: MyScaffold(),
  ));
}

确保在你的pubspec.yaml文件的flutter节中具有 uses-material-design: true 键值对,这将允许你使用 Material icons(一套谷歌的图标集合) 的预定义设置

name: my_app
flutter:
  uses-material-design: true

许多 Material Design widgets 需要位于MaterialApp来体现其特性,以内置其theme data。因此需要以 MaterialApp 来运行此应用(参照上面的综合示例中的 runApp(MaterialApp....)

MyAppBar widget 创建了一个内部左右padding为8像素,高56逻辑像素(独立于设备的)Container.在Container内部,MyAppBar使用了Row 布局来组织其子项。位于其中间的子项,以Expanded作为title widget的标记,意味着它将随着自身的内容填充满未被其他子项填充的空间。。你可以有多个Expanded的子项,并使用flex参数来限定每个子项展开大小的比重。

MyScaffold widget使用纵向column组织其子项。在column的顶部放置了一个MyAppBar的实例,传给程序栏一个Text widget作为其标题。给其他widget传递widget作为参数是一项强大的技术,它使你可以创建能够广泛服用的通用部件(widgets)。最终,MyScaffold使用了一个Expanded在它的body中来填充多余的空间。它由一个居中的消息内容构成。

想要学习更多的内容,请查看 Layouts 布局介绍。

使用 Material 组件

Flutter提供了一套遵循Material Design风格的为您创建app提供巨大帮助的widget。一个Material app起始于MaterialApp部件,一个位于你的app根部节点的由诸多有用部件组成的widget,包括管理由字符串标识的widget堆栈 Navigator,或者我们称之为“routes”(路由)。Navigator使我们能够实现在app中平滑得进行页面跳转。是否使用MaterialApp是完全可选的,但它也是能够提供非常好的体验。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

现在,这些源码从MyAppBar和MyScaffold切换到了AppBar和Scaffold widgets,其引用了 material.dart,应用看起来更加扁平化。例如,应用栏具有了阴影而且标题文本也自动且正确地位于其内部。并且一个浮动操作按钮也被添加了上去。

请注意,这些widgets被作为参数传递给了其他idgets,Scaffold widget使用了很多不同的widgets作为其命名的参数,每个widget都位于Scaffold布局中恰当的位置。同样的,AppBar widget是你传入了一些widget作为头部widget和标题的操作设置。这种方式在框架中十分常见,在设计属于自己的widgets时,希望你能够多考虑这一点。

更多信息,请查阅Material Components widgets

注意:Material是flutter包含的两种设计方式的其中之一,如若是要创建ios为主的app设计,请查阅 Cupertino components 包,它具有单独的CupertinoApp, 和CupertinoNavigationBar版本。

手势处理

大多数应用,都具有不同形式的系统交互方式。创建一个具有交互性的应用的第一步,就是检测用户的手势输入。这里我们来看一下如何通过创建一个简单的按钮来实现它。

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 36.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(5.0),
          color: Colors.lightGreen[500],
        ),
        child: Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}

GestureDetectorwidget不具有外观,它只是用来监视用户的手势输入。当用户点击Container,GestureDetector就会调用它的onTap() 回调函数,在这个示例会在控制台中打印出一条消息。你能使用GestureDetector来检测许多输入手势,包括点击、拖拽以及缩放。

大多数widget使用GestureDetector来为之提供可选的回调功能。例如 IconButton, RaisedButton, 以及FloatingActionButton具有onPressed()回调则由用户点击widget来触发。

更多信息,请查阅Gestures in Flutter(Flutter中的手势).

widget响应输入

到目前为止,本章节还只有使用过无状态的widget。无状态widget从它的父级widget接收存储于常量中的参数。当一个widget被请求创建:build() ,它将使用这些参数为其新创建的widget派生新的参数。

为了创建更加复杂的场景,例如,为响应更有趣的用户录入的方式,应用程序通常带有一些状态。Flutter使用StatefulWidgets来抓住这种想法。StatefulWidgets是一组知道如何生成状态对象的特殊的widgets,它们被用来保持状态。仔细查看以下例子,使用前面提到过的RaisedButton

class Counter extends StatefulWidget {
  // This class is the configuration for the state. It holds the
  // values (in this case nothing) provided by the parent and used
  // by the build  method of the State. Fields in a Widget
  // subclass are always marked "final".

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework that
      // something has changed in this State, which causes it to rerun
      // the build method below so that the display can reflect the
      // updated values. If you change _counter without calling
      // setState(), then the build method won't be called again,
      // and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return Row(
      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}

你可能会好奇为什么StatefulWidget和State是两个分离的对象,在Flutter中,这两个类型的对象具有不同的生命周期。Widgets是临时对象,用于构造应用程序当前状态的表示形式。State对象在另一方面,在build()调用之间它是持久的,允许它们记住信息。

上面的示例允许用户输入并直接将其结果应用于build()方法。在更多复杂的应用当中,不同层次结构的widget的不同部分,有可能需要负责不同的关注点(切面?)。例如,一个widget也许具有复杂的用户界面来收集信息,例如日期或地点,而其他的widget有可能使用这些信息来修改总体介绍。

在Flutter中,通过回调将通知流“上移”到widget的层次,而当前状态流则被下移到无状态widget来做演示。重定向数据流便是state。下面的稍复杂一些的示例将展示它的运作方式。

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: onPressed,
      child: Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(children: <Widget>[
      CounterIncrementor(onPressed: _increment),
      CounterDisplay(count: _counter),
    ]);
  }
}

注意新创建的两个无状态wigets,清晰地分离了计数器的关注点:显示计数 CounterDisplay 和修改计数 CounterIncrementor,尽管其最终结果于之前的示例相同。职责分离允许将更复杂的特性封装于单个widget中,同时又保持了其在父级widget的简单性。

更多信息,请查阅:

将这些内容集合在一起

  • [ ] 未完待续

筋肉兔子
5 声望0 粉丝