从头学 Dart 第二集

153 阅读5分钟

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

  1. 关于自定义 widget 提示没有 key 我们可以通过下面的方式解决:
class GradientContainer extends StatelessWidget {
   const GradientContainer({key}):super(key: key);
   @override
   Widget build(context){
      return ...
   }
}

其中,GradientContainer 相当于 constructor 而 {key} 相当于结构自动注入的入参,而 :super(key: key) 则是调用了父级的构造函数并将结构的 key 传入父级的构造函数。

或者使用更加简单的写法,如下所示:

class GradientContainer extends StatelessWidget {
   const GradientContainer({super.key});
   @override
   Widget build(context){
      return ...
   }
}
  1. 代码划分,我们新建一个 dart 文件,然后将构建的 class 直接放入其中,并在原来的位置对其进行引用

    • 构建 gradient_container.dart 文件
    • 引入必要的依赖:
       import 'package:flutter/material.dart';
       class GradientContainer extends StatelessWidget {
          const GradientContainer({super.key});
    
          @override
          Widget build(context){
             return ...
          }
       }
    
    • 在原来的位置引入分离的代码并使用:
       import 'package:flutter/material.dart';
       import 'package:first_app/gradient_container.dart';
    
       void main() {
          runApp(
             const MaterialApp(
                home: Scaffold(
                   body: GradientContainer(),
                )
             )
          )
       }
    
  2. 使用 dart 中的变量 在上面的笔记中,我们完成了渐变色的设定,现在我们定义两个变量来完成此工作:

var startAlignment = Alignment.topLeft;
var endAlignment = Alignment.bottomRight;

当然定义变量的位置可以在 class 之外也可以在 class 之内,如下所示:

@override
Widget build(context) {
   startAlignment = Alignment.topLeft;
   return ...
}

在这种方式中,我们就没有在变量定义之前添加任何修饰符,私以为这是局部变量。

  1. 使用 var 定义的变量是可变的,而 const 定义的是不可变的,原来可变在某次赋值之后又不可变则使用 final:
const startAlignment = Alignment.topLeft;
const endAlignment = Alignment.bottomRight;

或者,

final startAlignment = Alignment.topLeft;
final endAlignment = Alignment.bottomRight;

通常在定义变量的时候直接初始化并使用 const 进行限制并不符合 finnal 的语义,因此上面的代码会被提示要更换成 const.(Use: 'const'for final variables initialized to a constant value. Try replacing 'final'with 'const'. )

  1. 类型构造参数 如下所示,我们封装组件要求其具有一定的灵活性,因此必须能够接受调用方传递的构造参数,接受参数的方式可以是位置传参或者命名传参,类似于上面 key 的传递:
const StyledText(String text, {super.key}); // Type annotations are added in front of parameters

上面采用的就是位置传参,并且在参数列表中我们使用 annotation 对形参的类型进行了限制.

此时,在调用方,参数应该传递为:styledText('Hello World!')

那么 key 又在那里呢?这里并没有传递 key 的值,其实 key 是可选的:You don't have to provide a value for the named key argument because named arguments, by default. are optional!

text 并不可以直接被使用,事实上,这里相当复杂,必须通过如下的方式让 class 内部能够 access 到这个值:

const StyledText(String text, {super.key}): outputText = text;
String outputText;

这样一来,我们就将形参 text 的值映射到 class 内部的 outputText 上,然后就可以在 class 内部使用了。

当然,这个参数和 key 本质上没有什么不同,因此也具有简写的方式:

StyledText(this.text,{super.key});

String text;

注意这里内部参数 text 和形参的名称必须是同名的。最佳实践是在其前面加上 final 如下所示:

StyledText(this.text,{super.key});

final String text;
  1. 传递复杂的构造参数 相比上面只传递一个 String 进来,下面我们尝试传递渐变色需要使用的颜色数组:
const startAlignment = Alignment.topLeft;
const endAlignment = Alignment.bottomRight;
...
...
...
const GradientContainer({super.key, required this.colors});

final List<Color> colors;

@override
Widget build(context){
   return Container(
      decoration: BoxDecoration(
         gradient: LinearGradient(
            colors: colors,
            begin: startAlignment,
            end: endAlignment,
         ),
      ),
   ),
}

如果我们在 {} 中的形参前面加上 required 修饰,则表示此形参的值一定会被传递,这样就避免了 gradient 无法接受空值的问题。

当然如果只从解决问题的角度,我们可以将颜色值分开传递,就如同传递 text 一样:

const GradientContainer(this.color1, this.color2, {super.key});

final Color color1;
final Color color2;

@override
Widget build(context){
   return Container(
      decoration: BoxDecoration(
         gradient: LinearGradient(
            colors: [color1, color2],
            begin: startAlignment,
            end: endAlignment,
         ),
      ),
   ),
}
  1. 静态资源引入 dart, 例如图片
  • 首先在根目录下面创建一个名为 asset 的目录,并在其中创建 images 子目录,然后将图片放入其中。
  • 接下来在根目录的 pubspec.yaml 文件中对其中的静态资源进行说明。在 pubspec.yaml 中找到 flutter 字段然后自动构建的项目应该有注释示范如何引入:
flutter:
   uses-material-design: true
assets:
   assets/images/dice-1.png
   assets/images/dice-2.png
   assets/images/dice-3.png
   assets/images/dice-4.png
   assets/images/dice-5.png
   assets/images/dice-6.png
  1. 从引入图片到构造函数重载 dart 中自定义的类可以有多个构造函数,如下所示:
import 'package:flutter/material.dart';

class GradientContainer extends StatelessWidget {
  final Color color1;
  final Color color2;
  
  const GradientContainer({Key? key, required this.color1, required this.color2}) : super(key: key);

  const GradientContainer.purple({Key? key}) : this(
    key: key,
    color1: Colors.deepPurple,
    color2: Colors.indigo,
  );

/*
  const GradientContainer.purple({super.key})
    : color1= Colors.deepPurple,
      color2 =Colors.indigo;
*/

  @override
  Widget build(BuildContext context) {
    // 构建渐变容器的代码
  }
}

上面的代码定义了两个重载的构造函数:

GradientContainer.purple
GradientContainer

这样当我们调用更加具体的构造函数的时候,将会获得更多的预设的参数,或者说可以传递较少的构造参数。

作为调用方,可以这样使用:

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: GradientContainer.purple(),
      ),
    ),
  );
}

正是由于构造函数重载机制的存在,我们才能够以如下的方式加载图片:

child: Center(
   child: Image.asset('assets/image/dice-2.png'),
),
  1. 布局组件 Row 和 Column 除了 Center 之外,我们还可以使用 Row 和 Column 进行布局,它们的介绍如下: 下面是以表格格式返回的信息:
Layout WidgetDescriptionMain AxisCross AxisDefault Behavior
Column()Can be used to place multiple child widgets next to each otherVertical AxisHorizontal AxisOccupies the entire available height but only the width required by its content (children)
Row()Can be used to place multiple child widgets next to each otherHorizontal AxisVertical AxisOccupies the entire available width but only the height required by its content (children)

这个表格概述了 Column()Row() 布局小部件的主要用途、主轴和交叉轴的方向,以及它们的默认行为。

  1. 实现经典布局(图片 + 按钮)
child: Center(
   child: Column(
      children: [
         Image.asset(
            'assets/image/dice-2.png',
            width: 200,
         ),
         TextButton(onPressed: handlePressed, child: const Text('Roll Dice'),),
      ],
   ),
)

上面的布局思路就是,先将一个列放在中间然后再在此列中从上到下放图片和按钮,列宽由最大子组件撑开。

不难发现,我们现在需要在 dart 中定义一个函数,以此来完成点击事件的回调。