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 - 屏幕适配
相关推荐
恋猫de小郭3 小时前
Compose Multiplatform iOS 稳定版发布:可用于生产环境,并支持 hotload
android·flutter·macos·ios·kotlin·cocoa
yuanlaile5 小时前
Flutter开发HarmonyOS实战-鸿蒙App商业项目
flutter·华为·harmonyos·flutter开发鸿蒙
WangMing_X5 小时前
Flutter开发IOS蓝牙APP的大坑
flutter·ios
程序猿阿伟12 小时前
《让内容“活”起来:Flutter社交应用瀑布流布局的破界实践》
前端·flutter
帅次16 小时前
Flutter TabBar / TabBarView 详解
android·flutter·ios·小程序·iphone·taro·reactnative
WDeLiang1 天前
Flutter 布局
前端·flutter·dart
b2894lxlx2 天前
flutter3.29 build.gradle.kts设置安卓签名
android·flutter
UzumakiHan2 天前
flutter权限允许访问
android·flutter
Ya-Jun2 天前
性能优化实践:渲染性能优化
android·flutter·ios·性能优化