MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter Widget基础入门与使用指南

2023-11-256.8k 阅读

一、Flutter Widget 概述

Flutter 中的一切皆为 Widget。Widget 是 Flutter 构建用户界面的基本元素,它描述了在屏幕上显示的一个矩形区域及其相关的行为。可以把 Widget 想象成组成用户界面的一个个积木块,通过组合不同的 Widget 来创建出复杂而丰富的用户界面。

Flutter 中的 Widget 分为两种类型:有状态(StatefulWidget)和无状态(StatelessWidget)。

1.1 无状态 Widget

无状态 Widget 是指那些一旦创建,其状态就不会发生变化的 Widget。这类 Widget 很适合用于展示一些固定不变的数据,比如文本标签、图标等。

以下是一个简单的无状态 Widget 示例,用于显示一个文本:

import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
  final String text;

  MyStatelessWidget({required this.text});

  @override
  Widget build(BuildContext context) {
    return Text(text);
  }
}

在上述代码中,MyStatelessWidget 继承自 StatelessWidget,并接收一个 text 参数,在 build 方法中返回一个 Text Widget 来显示传入的文本。

1.2 有状态 Widget

有状态 Widget 则相反,其状态可以在 Widget 的生命周期内发生变化。例如,一个开关按钮,用户点击后它的状态会从“关”变为“开”。有状态 Widget 由两部分组成:状态(State)和 Widget 本身。

下面是一个简单的有状态 Widget 示例,实现一个计数器:

import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_counter'),
        ElevatedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

在上述代码中,MyStatefulWidget 继承自 StatefulWidget,并通过 createState 方法创建一个 _MyStatefulWidgetState 对象。_MyStatefulWidgetState 继承自 State,在其中定义了一个 _counter 变量来保存计数器的值,以及一个 _incrementCounter 方法用于增加计数器的值。在 build 方法中,根据 _counter 的值显示当前计数,并提供一个按钮用于触发计数增加操作。

二、常用基础 Widget

2.1 文本 Widget - Text

Text Widget 用于在屏幕上显示文本。它提供了丰富的属性来定制文本的外观,如字体大小、颜色、对齐方式等。

Text(
  'Hello, Flutter!',
  style: TextStyle(
    fontSize: 24,
    color: Colors.blue,
    fontWeight: FontWeight.bold,
  ),
  textAlign: TextAlign.center,
)

在上述代码中,style 属性用于设置文本的样式,textAlign 属性用于设置文本的对齐方式。

2.2 按钮 Widget - ElevatedButton

ElevatedButton 是一种带有阴影和填充背景的按钮。它可以响应用户的点击操作,执行相应的逻辑。

ElevatedButton(
  onPressed: () {
    // 按钮点击逻辑
    print('Button clicked');
  },
  child: Text('Click me'),
)

在上述代码中,onPressed 属性指定了按钮被点击时执行的回调函数,child 属性指定了按钮上显示的文本。

2.3 容器 Widget - Container

Container 是一个非常常用的 Widget,它可以包含其他 Widget,并提供了布局、装饰等功能。

Container(
  width: 200,
  height: 100,
  color: Colors.yellow,
  child: Center(
    child: Text('Inside Container'),
  ),
)

在上述代码中,widthheight 属性设置了容器的尺寸,color 属性设置了容器的背景颜色,child 属性指定了容器内部包含的 Widget。

2.4 图像 Widget - Image

Image Widget 用于在界面上显示图片。可以从本地资源、网络或内存中加载图片。

从本地资源加载图片:

Image.asset('assets/images/flutter_logo.png')

从网络加载图片:

Image.network('https://example.com/image.jpg')

在上述代码中,Image.asset 用于从本地资源加载图片,Image.network 用于从网络加载图片。

三、Widget 布局

Flutter 提供了强大的布局系统,通过组合不同的布局 Widget 来实现灵活的界面布局。

3.1 线性布局 - Row 和 Column

Row 用于水平排列子 Widget,Column 用于垂直排列子 Widget。

Row(
  children: [
    Container(
      width: 100,
      height: 100,
      color: Colors.red,
    ),
    Container(
      width: 100,
      height: 100,
      color: Colors.green,
    ),
  ],
)

Column(
  children: [
    Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
    Container(
      width: 100,
      height: 100,
      color: Colors.orange,
    ),
  ],
)

在上述代码中,Row 中的两个 Container 会水平排列,Column 中的两个 Container 会垂直排列。

3.2 弹性布局 - Flexible 和 Expanded

FlexibleExpanded 用于在 RowColumn 中分配剩余空间。Expanded 实际上是 Flexible 的一种特殊情况,它的 flex 属性默认为 1。

Row(
  children: [
    Expanded(
      child: Container(
        color: Colors.red,
      ),
    ),
    Flexible(
      flex: 2,
      child: Container(
        color: Colors.green,
      ),
    ),
  ],
)

在上述代码中,Row 中的两个 Container 会根据 ExpandedFlexibleflex 属性分配剩余空间。ExpandedContainer 会占据一份空间,Flexibleflex 为 2)的 Container 会占据两份空间。

3.3 层叠布局 - Stack 和 Positioned

Stack 允许子 Widget 堆叠在一起,Positioned 用于指定子 Widget 在 Stack 中的位置。

Stack(
  children: [
    Container(
      width: 200,
      height: 200,
      color: Colors.grey,
    ),
    Positioned(
      top: 50,
      left: 50,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    ),
  ],
)

在上述代码中,第一个 Container 作为底层背景,第二个 Container 通过 Positioned 定位在 Stack 的指定位置。

四、Widget 交互

4.1 按钮点击交互

如前文所述,通过为 ElevatedButton 等按钮 Widget 设置 onPressed 属性来处理按钮点击事件。

4.2 文本输入交互

TextField 用于接收用户的文本输入。可以通过 controller 属性来控制输入内容,并监听输入变化。

class MyTextFieldWidget extends StatefulWidget {
  @override
  _MyTextFieldWidgetState createState() => _MyTextFieldWidgetState();
}

class _MyTextFieldWidgetState extends State<MyTextFieldWidget> {
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _controller.addListener(() {
      print('Text changed: ${_controller.text}');
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: InputDecoration(
        labelText: 'Enter text',
      ),
    );
  }
}

在上述代码中,TextEditingController 用于控制 TextField 的输入内容,并通过 addListener 方法监听输入变化。在 initState 方法中注册监听器,在 dispose 方法中释放控制器资源。

4.3 手势交互

Flutter 提供了 GestureDetector Widget 来检测各种手势,如点击、长按、拖动等。

GestureDetector(
  onTap: () {
    print('Tapped');
  },
  onLongPress: () {
    print('Long pressed');
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.yellow,
  ),
)

在上述代码中,GestureDetector 包裹了一个 Container,并为其设置了 onTap(点击)和 onLongPress(长按)回调函数。

五、Widget 样式与主题

5.1 样式设置

每个 Widget 都有自己的样式属性,如 TextstyleContainerdecoration 等。通过设置这些属性来定制 Widget 的外观。

Text(
  'Styled Text',
  style: TextStyle(
    fontSize: 30,
    color: Colors.purple,
    decoration: TextDecoration.underline,
  ),
)

在上述代码中,通过 TextStyle 设置了 Text 的字体大小、颜色和下划线装饰。

5.2 主题设置

Flutter 允许通过 Theme 来统一应用程序的外观风格。可以在 MaterialApp 中设置主题。

MaterialApp(
  theme: ThemeData(
    primarySwatch: Colors.blue,
    textTheme: TextTheme(
      headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
    ),
  ),
  home: Scaffold(
    appBar: AppBar(
      title: Text('Theme Example'),
    ),
    body: Text('This is a themed text'),
  ),
)

在上述代码中,ThemeData 设置了应用的主色调为蓝色,并定义了 headline1 的文本样式。应用中的 AppBarText 会根据主题设置呈现相应的外观。

六、Widget 组合与复用

6.1 自定义 Widget 组合

通过将多个基础 Widget 组合在一起,可以创建出更复杂、更具功能性的自定义 Widget。

class MyCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Custom Widget Title'),
        Container(
          width: 200,
          height: 100,
          color: Colors.green,
        ),
      ],
    );
  }
}

在上述代码中,MyCustomWidget 组合了 TextContainer Widget,形成了一个自定义的 Widget。

6.2 Widget 复用

一旦创建了自定义 Widget,就可以在多个地方复用它,提高代码的可维护性和可扩展性。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MyCustomWidget(),
        MyCustomWidget(),
      ],
    );
  }
}

在上述代码中,MyHomePage 中复用了两次 MyCustomWidget

七、Widget 生命周期

对于有状态 Widget,了解其生命周期非常重要。

7.1 创建阶段

  • initState:在 Widget 插入到树中时调用,只调用一次。常用于初始化一些数据,如 TextEditingController
  • didChangeDependencies:在 initState 之后调用,并且每当 State 对象的依赖发生变化时也会调用。

7.2 更新阶段

  • build:每当 State 对象的状态发生变化时,Flutter 框架会调用 build 方法来重建 Widget。
  • didUpdateWidget:当 Widget 的配置(如传入的参数)发生变化时调用。

7.3 销毁阶段

  • deactivate:当 Widget 从树中移除时调用。
  • dispose:在 deactivate 之后调用,用于释放资源,如取消动画、关闭流等。

以下是一个展示有状态 Widget 生命周期方法调用的示例:

class LifecycleWidget extends StatefulWidget {
  @override
  _LifecycleWidgetState createState() => _LifecycleWidgetState();
}

class _LifecycleWidgetState extends State<LifecycleWidget> {
  @override
  void initState() {
    super.initState();
    print('initState called');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('didChangeDependencies called');
  }

  @override
  void didUpdateWidget(LifecycleWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget called');
  }

  @override
  void deactivate() {
    super.deactivate();
    print('deactivate called');
  }

  @override
  void dispose() {
    super.dispose();
    print('dispose called');
  }

  @override
  Widget build(BuildContext context) {
    print('build called');
    return Container();
  }
}

通过在各个生命周期方法中打印日志,可以观察到它们的调用时机。

八、Widget 性能优化

8.1 使用 const Widget

如果一个 Widget 的状态永远不会改变,并且它的子 Widget 也不会改变,那么可以将其声明为 const。这样 Flutter 可以在编译时进行优化,减少内存开销。

const Text('This is a const text')

8.2 避免不必要的重建

通过合理使用 StatefulWidgetStatelessWidget,以及优化 build 方法中的逻辑,避免不必要的 Widget 重建。例如,将一些不变的计算逻辑移出 build 方法。

class MyOptimizedWidget extends StatefulWidget {
  @override
  _MyOptimizedWidgetState createState() => _MyOptimizedWidgetState();
}

class _MyOptimizedWidgetState extends State<MyOptimizedWidget> {
  int _data = 0;

  void _updateData() {
    setState(() {
      _data++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 避免在build方法中进行复杂的、不变的计算
    final result = _calculateResult();
    return Column(
      children: [
        Text('Result: $result'),
        ElevatedButton(
          onPressed: _updateData,
          child: Text('Update'),
        ),
      ],
    );
  }

  int _calculateResult() {
    // 假设这是一个复杂的计算
    return _data * 2;
  }
}

在上述代码中,将复杂的计算 _calculateResult 方法移出 build 方法,避免了每次 build 时重复计算。

8.3 使用 AutomaticKeepAliveClientMixin

当一个 Widget 在页面切换或滚动过程中需要保持其状态时,可以使用 AutomaticKeepAliveClientMixin。例如,在一个 TabBarView 中,如果某个 Tab 的内容需要保持状态不被销毁,可以让其对应的 State 类混入 AutomaticKeepAliveClientMixin

class MyTabWidget extends StatefulWidget {
  @override
  _MyTabWidgetState createState() => _MyTabWidgetState();
}

class _MyTabWidgetState extends State<MyTabWidget> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
      // 内容
    );
  }
}

在上述代码中,wantKeepAlive 返回 true 表示希望保持该 Widget 的状态。

通过深入学习和掌握 Flutter Widget 的基础知识、布局、交互、样式、复用、生命周期以及性能优化等方面,开发者可以更加高效地构建出美观、流畅且功能丰富的 Flutter 应用程序。在实际开发中,不断实践和积累经验,能够更好地运用 Widget 来实现各种复杂的用户界面需求。同时,随着 Flutter 的不断发展和更新,Widget 相关的特性和功能也会不断完善和扩展,开发者需要持续关注和学习,以跟上技术的发展步伐。