Flutter - 概览

Hello world

⌘ + shift + p

选择 Empty Application 模板

dart 复制代码
// 导入Material风格的组件包
// 位置在flutter安装目录/packages/flutter/lib/material.dart
import 'package:flutter/material.dart';

void main() {
  // runApp函数接收MainApp组件并将这个Widget作为根节点
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  // Describes the part of the user interface represented by this widget.

  // The framework calls this method when this widget is inserted into the tree in  a given 
  // [BuildContext] and when the dependencies of this widget change 
  // (e.g., an [InheritedWidget] referenced by this widget changes). 
  // This method can potentially be called in every frame and should not have any  
  // side effects beyond building a widget.
  Widget build(BuildContext context) {
    /// An application that uses Material Design.
    /// 使用Material设计的组件,home代表默认页
    return const MaterialApp(
      /// The Scaffold is designed to be a top level container for
      /// a [MaterialApp]. This means that adding a Scaffold
      /// to each route on a Material app will provide the app with
      /// Material's basic visual layout structure.
      /// Scaffold,MateriaApp组件的顶层容器,规范样式之类的
      home: Scaffold(
        body: Center(  /// 局中显示Hello World
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

build方法用于描述Widget的展示效果,当被添加到上下文的树Widget发生变化时会触发这个方法。因为这个方法是高频操作所以不应该有副作用。

热重载(Hot reload)

Flutter支持热重载,无需重启启动应用的情况下去重新刷新页面。通过将更新代码注入到运行的Dart虚拟机来实现热重载。在虚拟机使用新的字段和函数更新类后,Flutter框架自动重新构建widget。

修改后直接保存/点击调试那里的闪电图标能直接刷新

有状态(StatefulWidget) + 无状态(StatelessWidget)

Flutter中的一切都是Widget,Widget分为有状态和无状态两种,在 Flutter 中每个页面都是一帧,无状态就是保持在那一帧,而有状态的 Widget 当数据更新时,其实是创建了新的 Widget,只是 State 实现了跨帧的数据同步保存。

比如上面的MainApp是无状态的Widget,而Scaffold是有状态的Widget

dart 复制代码
class MainApp extends StatelessWidget {
...
}

class Scaffold extends StatefulWidget {
...
}

创建新组件时继承有状态还是无状态的Widget取决于是否要管理状态

基础组件

Text

Text 是现实单一样式的文本字符串组件。字符串可能跨多行中断,也可能全部显示在同一行上,取决于布局约束

dart 复制代码
Widget build(BuildContext context) {
    return MaterialApp(
      home:Scaffold(
        body:Center(
          // 设置宽度限制为100点
          child:Container(
              width: 100,
              height:30,
              // 边框
              decoration: BoxDecoration(border: Border.all()),
              // TextOverflow.ellipsis 超过部分用...
              // TextOverflow.clip -- Clip the overflowing text to fix its container. 超出部分换下一行,外部容器会被遮挡
              // TextOverflow.visible -- Render overflowing text outside of its container. 超出容器部分能渲染
              child: Text(overflow:TextOverflow.ellipsis, 'Hello world, how are you?'))
      ))
    );
  }

TextOverflow.ellipsis的效果

TextOverflow.clip 的效果

TextOverflow.visible 的效果

maxLines 控制最大行数
softWrap 控制是否换行

overflowTextOverflow.visible

softWrap: false

softWrap: true

Text.rich

使用Text.rich构造器,Text组件可以在一个段落中展示不同的样式

dart 复制代码
Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: const Text.rich(
            TextSpan(
              text: 'Hello', // default text style
              children: <TextSpan>[
                TextSpan(
                  text: ' beautiful ',
                  style: TextStyle(fontStyle: FontStyle.italic),
                ),
                TextSpan(
                  text: 'world',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
关于Text的交互

GestureDetector widget包装Text,然后在GestureDetector.onTap中处理点击事件。或者使用TextButton来代替

Row,Column,Stack,Container

  • Container: 只有一个子 Widget。默认充满,包含了padding、margin、color、宽高、decoration 等配置
  • Row: 可以有多个子 Widget。水平布局。
  • Column: 可以有多个子 Widget。垂直布局。
  • Stack: 可以有多个子 Widget。 子Widget堆叠在一起。
  • Center: 只有一个子 Widget。只用于居中显示,常用于嵌套child,给child设置居中。
  • Padding: 只有一个子 Widget。只用于设置Padding,常用于嵌套child,给child设置padding。
  • Expanded: 只有一个子 Widget。在 Column 和 Row 中充满。
  • ListView: 可以有多个子Widget,列表布局
dart 复制代码
// 水平布局
Row(
    children: [
      // 图标
      const IconButton(
        icon: Icon(Icons.menu),
        tooltip: 'Navigation menu',
        onPressed: null, // null disables the button
      ),
      // Expanded expands its child
      // to fill the available space.
      // 填充满2个图标之间的空间
      Expanded(child: title),
      // 查询图标
      const IconButton(
        icon: Icon(Icons.search),
        tooltip: 'Search',
        onPressed: null,
      ),
    ],
)
dart 复制代码
// 垂直布局
Column(
    children: [
      MyAppBar(
        title: Text(
          '示例标题',
          style:
              Theme.of(context) //
              .primaryTextTheme.titleLarge,
        ),
      ),
      const Expanded(child: Center(child: Text('容器'))),
    ],
)

要使用material中这些预定义图标,需要将工程中的pubspec.yaml文件里的uses-material-design字段设置为true

使用Material组件

dart 复制代码
import 'package:flutter/material.dart';

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

class TutorialHome extends StatelessWidget {
  const TutorialHome({super.key});

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

使用ScaffoldAppBar替换原来自定义的MyScaffoldMyAppBar

手势处理

dart 复制代码
@override
Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: GestureDetector(
            child: Text('Hello world',overflow: TextOverflow.ellipsis,),
            onTap: ()=> {
            // 生产环境不要用print
              print("123")
            },)
        ),
      ),
    );
}

更改小组件以响应输入

UI通常需要对用户的输入进行响应,比如点外卖时根据用户选择菜品计算最后的价格, Flutter中使用StatefulWidgets来处理这种场景。

继承StatefulWidget,重写createState方法

dart 复制代码
class Counter extends StatefulWidget {
  const Counter({super.key});

  // 继承StatefulWidget的类要重写createState()方法,内容返回是_CounterState对象
  // ``=>`` (胖箭头)简写语法用于仅包含一条语句的函数。该语法在将匿名函数作为参数传递时非常有用
  @override
  State<Counter> createState() => _CounterState();
}

所有的类都隐式定义成了一个接口。因此,任意类都可以作为接口被实现 ,定义一个继承State并实现Counter类的方法

dart 复制代码
/// [State] objects are created by the framework by calling the
/// [StatefulWidget.createState] method when inflating a [StatefulWidget] to
/// insert it into the tree. 
/// 在这里当Counter组件被添加到渲染树时,因为也实现了Counter类,所以会调用对应的createState方法。
class _CounterState extends State<Counter> {
  int _counter = 0;
  
  // _ 代表私有方法
  void _increment() {
    // 调用setState()通知Flutter状态变化了,然后重新执行build方法实现实时刷新的效果
    setState(() {
      _counter++;
    });
  }

合并的示例

dart 复制代码
import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({
    required this.product,
    required this.inCart,
    required this.onCartChanged,
  }) : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    return inCart //
        ? Colors.black54
        : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      // 5. 点击Item时调用传入 onCartChanged 回调,并传入一开始接收的出参数
      // 比如一开始在订单内inCart传true
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      // 4.显示产品订单的样式
      // 10.根据新参数重新显示样式
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

class ShoppingList extends StatefulWidget {
  // 要求传入 products属性
  const ShoppingList({required this.products, super.key});
  
  final List<Product> products;
  
  // 2. 调用_ShoppingListState创建状态对象
  @override
  State<ShoppingList> createState() => _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  final _shoppingCart = <Product>{};
  // 6. 点击触发回调
  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // 7. 根据入参进行判断,如果一开始是true,则移除,否则添加,即取反操作
      if (!inCart) {
        _shoppingCart.add(product);
      } else {
        _shoppingCart.remove(product);
      }
      // 8. 通知Flutter 重新执行_ShoppingListState对象的build方法
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Shopping List')),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 8),
        children:
            // 3. 根据products属性创建ShoppingListItem,并传入产品信息,回调
            // 9. 再次调用ShoppingListItem并传入新参数
            widget.products.map((product) {
              return ShoppingListItem(
                product: product,
                inCart: _shoppingCart.contains(product),
                onCartChanged: _handleCartChanged,
              );
            }).toList(),
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      title: 'Shopping App',
      // 1. 创建ShoppingList对象,并传入Product参数
      home: ShoppingList(
        products: [
          Product(name: 'Eggs'),
          Product(name: 'Flour'),
          Product(name: 'Chocolate chips'),
        ],
      ),
    ),
  );
}

响应组件的生命周期相关事件

Flutter调用createState方法后,会将state对象添加到渲染树并且调用state对象的initState(),可以重写这个方法中配置动画或准备订阅平台相关的服务,重写方法开始要先调用super.initState。当state对象不再需要时,Flutter会调用对象的dispose方法来执行清理操作,比如取消定时器,取消订阅,同样在重写方法中也要先调用super.dispose

其它

包缓存地址

sh 复制代码
$ flutter pub get 
# 命令下载的包在~/.pub-cache/hosted

参考

  1. Flutter
  2. Flutter-UI
  3. Flutter - 我给官方提PR,解决run命令卡住问题 😃
  4. Day16 - Flutter - 屏幕适配
相关推荐
火柴就是我1 天前
flutter 之真手势冲突处理
android·flutter
Speed1231 天前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭1 天前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone1 天前
从flutter源码看其渲染机制
android·flutter
ALLIN2 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei2 天前
Flutter 国际化
flutter
Dabei2 天前
Flutter MQTT 通信文档
flutter
Dabei2 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉2 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter