从头学 Dart 第三集

129 阅读6分钟

最近打算自己做一个电商 App 调研之后选择技术栈为 Flutter 因此打算梳理一下 Flutter 的所有知识点,做个预热。

  1. 对于 Column 组件,我们设置 mainAxisSize: MainAxisSize.min 之后,其中的内容就自动居中了。原理很有可能是原来是沾满整个高度方向并且居中的,现在设置成为内容撑开之后虽然高度变化了,但是默认居中还在,于是就自动跑到屏幕的中间了。

在 Flutter 框架中,Column组件是一个线性布局的组件,它将子组件沿着主轴(默认是垂直方向)排列。mainAxisSize属性决定了Column的主轴尺寸行为。

MainAxisSize.minMainAxisSize.maxMainAxisSize的两个可能值:

  • MainAxisSize.max:这是默认值。当设置为max时,Column会尝试占据尽可能多的空间,即它会扩展以填充其父组件分配给它的空间。在这种情况下,如果Column中的子组件没有指定高度,Column会扩展以填满整个可用空间,使得子组件默认情况下不会居中,而是会堆叠在一起。

  • MainAxisSize.min:当设置为min时,Column会根据其子组件的实际大小来确定自己的大小,而不是尝试占据所有可用空间。这意味着Column会收缩到其子组件所需的最小尺寸。如果子组件没有填满整个空间,Column会保持在子组件的大小,而不会扩展。

当你将ColumnmainAxisSize设置为MainAxisSize.min时,Column的高度将由其子组件的最小尺寸决定。如果Column的子组件没有填满整个可用空间,并且Column内部没有额外的对齐或间距设置,那么Column中的子组件将会保持它们在主轴上的默认对齐方式,这通常是居中对齐。因此,如果Column的子组件没有指定特定的对齐方式,它们将会在Column的中心垂直堆叠。

总结来说,当你设置mainAxisSize: MainAxisSize.min,你实际上是在告诉Column不要扩展以填满所有可用空间,而是根据其子组件的实际大小来确定自己的大小。如果子组件没有填满整个空间,它们将在Column中居中对齐,因为这是Column在没有额外对齐设置时的默认行为。

  1. 我们通过如下的方式增加一个按钮组件并通过 TextButton.styleFrom 设置其样式:
TextButton(
  onPressed: rollDice, // 确保rollDice是一个定义好的函数
  style: TextButton.styleFrom(
    padding: const EdgeInsets.only(top: 20), // 设置按钮的内边距
    foregroundColor: Colors.white, // 设置按钮的前景色,这里假设是文字颜色
    textStyle: const TextStyle(
      fontSize: 28, // 设置文字样式的字体大小
    ),
    child: const Text('Roll Dice'), // 设置按钮显示的文本
  ),
)
  1. 为了在高度方向上留出空隙,除了给下面的元素增加 padding 之外(如上述代码所示),我们可以利用 SizedBox 组件进行占位:const SizeBox(height: 20),
  2. 响应式组件基础 为了实现组件随着某个事件的发生(这通常可能是按钮被点击)而做出改变,我们必须使用带有状态的 widget 而不是无状态的组件;实现响应式的一个基本思路,非常类似于 Echarts 的刷新,那就是当回调函数被执行之后,调用组件的重载方法(setState)并将新的状态传递给此重载方法,注意这个和 Vue 不同,组件的刷新不是理所应该或者自发的,而是需要手动调用重载的方法,类似于 React.
  3. Flutter 的调试代码是 print(123) 等同于 console.log(123)
  4. 在 Dart 中以下划线开头的 identifor 是私有的,类名等。
  5. StatefulWidget 类是实现响应式组件的基础:
import 'package:flutter.material.dart';

class DiceRoller extends StatefulWidget {
   const DiceRoller({super.key});
   @override
   State<DiceRoller> createState(){
      return _DiceRollerState();
   }
}

class _DiceRollerState extends State<DiceRoller> {
   var color = 1;

   void changeColor(){
      setState((){
         color = 2;
      })
   }
   @override
   Widget build(context) {
      return Column(...)
   }
}

上面的代码就是创建一个可变组件的基本过程。对于上面的代码,有一下几点需要说明:

  • 在 class 作用域之内,我们可以通过 var 直接创建变量,可以直接通过 void 创建函数,这与 js 语法有着相当大的不同;
  • dart 中的匿名函数形如 (){} 这个和 js 也是不同的 ()=>{};
  • setState 是一个函数,接受一个形参函数,是内置对象,无需再引入。
  1. Dart 中产生随机数:Random.nextInt(10) 表示产生一个 0-9 的随机数,不难看出是左闭右开的;使用 Random 需要引入一个库,名为:import 'dart:math';
  2. Dart 中的 template 类似于 js 但是不需要加花括号,这样看来更像 shell 语法:'assets/images/dice-$diceRoll.png'; 但不使用反引号。
  3. 一个点击按钮切换图片的可变组件示例:
class _DiceRollerState extends State<DiceRoller> {
  int currentDiceRoll = 2; // 初始化骰子的当前点数为2

  // 掷骰子的方法
  void rollDice() {
    setState(() {
      currentDiceRoll = Random().nextInt(6) + 1; // 生成1到6的随机数
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Image.asset(
          'assets/images/dice-$currentDiceRoll.png',
          width: 200,
        ),
        // 这里似乎有代码错误,我将假设你想要添加一个按钮来掷骰子
        ElevatedButton(
          onPressed: rollDice, // 当按钮被按下时,调用rollDice方法
          child: Text('Roll Dice'),
        ),
      ],
    );
  }
}

可以针对 Random 部分进行优化:

  final randomizer = Random();
  int currentDiceRoll = 2; // 初始化骰子的当前点数为2

  // 掷骰子的方法
  void rollDice() {
    setState(() {
      currentDiceRoll = randomizer.nextInt(6) + 1; // 生成1到6的随机数
    });
  }

这样一来我们就减少了 Random 的实例化次数,因为在临时变量前面加上了名为 final 的修饰符。

  1. Stateless Widget versus Stateful Widget

    Stateless WidgetsStateful Widgets
    不管理任何内部数据管理内部数据(“状态”)
    仅在父组件更新(“重新渲染”)时更新屏幕当状态变化时,组件被重新渲染,并且 UI 更新
    应作为默认选择:尽可能多地使用每当有变化的数据需要引起 UI 更新时使用
  2. dart 文件的连接符号应该是下划线而不是小驼峰或者大驼峰格式:start_screen.dart

  3. Center 组件只是将其子组件在水平和垂直方向上居中对齐,并不会影响子组件的大小。如果子组件的大小不足以填满屏幕,那么 Center 会将子组件居中显示,周围会有空白。

@override
Widget build(context) {
  return const Center(child: Text('Start Screen'));
}
  1. 一个上图下文组件示例
children: [
  Image.asset(
    'assets/images/quiz-logo.png',
    width: 300,
  ),
  const SizedBox(height: 80),
  const Text(
    'Learn Flutter the fun way!',
    style: TextStyle(
      color: Colors.white,
    ),
  ),
]
  1. 配置一个 outlined button
OutlinedButton(
  onPressed: () {
    // 在这里添加按钮点击时的逻辑
  },
  style: OutlinedButton.styleFrom(
    foregroundColor: Colors.white, // 设置按钮的前景色为白色
  ),
  child: const Text('Start Quiz'), // 设置按钮上的文本
)

我们还可以使用其另外一个构造函数,方便的写出带图标的按钮:

OutlinedButton.icon(
  onPressed: () {
    // 在这里添加按钮点击时的逻辑
  },
  style: OutlinedButton.styleFrom(
    foregroundColor: Colors.white, // 设置按钮的前景色为白色
  ),
  icon: const Icon(Icons.arrow_right_alt),
  label: const Text('Start Quiz'), // 设置按钮上的文本
)
  1. 两种设置透明度的方法 一种是使用 Opacity 组件将对象组件进行包裹,这个外包组件会修改被包裹的组件整体的透明度,开销比较大,也不是很灵活:
Opacity(
  opacity: 0.6,
  child: Image.asset(
    'assets/images/quiz-logo.png',
    width: 300,
  ),
)

另外一种就是使用具有透明度的颜色,如 ARGB 中的 A 表示的就是透明度,或者填充度,为 1 时颜色最饱和。

Image.asset(
  'assets/images/quiz-logo.png',
  width: 300,
  color: const Color.fromARGB(150, 255, 255, 255),
)

这个方法当然可以用来创建具有透明度的任何颜色,比如字体颜色。

在 Flutter 中,Image 组件的 color 属性可以用来对图片进行着色。通过设置 color 属性,你可以改变图片的颜色,而不需要修改原始图片文件。