Flutter从入门到放弃

120 阅读8分钟

想象一下,你是一名怀揣着梦想的开发者,渴望开发出一款能够在多个平台上流畅运行的移动应用。然而,传统的开发方式需要为不同的平台编写不同的代码,这不仅耗费大量的时间和精力,还容易出现兼容性问题。就在你感到困惑和迷茫的时候,Flutter 出现了,它就像一道曙光,为你照亮了前行的道路。

Flutter 是 Google 推出的一款开源 UI 框架,它允许开发者使用一套代码库来为多个平台开发移动应用,包括 iOS 和 Android。使用 Flutter,你可以告别繁琐的多平台开发,专注于实现应用的功能和设计。

在开始学习 Flutter 之前,我们需要做好一些准备工作。首先是搭建开发环境,这就像是为我们的开发之旅打造一艘坚固的船。搭建开发环境的步骤并不复杂,下面为你详细介绍:

  1. 安装 Dart SDK:Flutter 使用 Dart 语言开发,所以我们需要先安装 Dart SDK。你可以从 Dart 官网下载并安装最新版本的 Dart SDK。

  2. 安装 Flutter SDK:通过 Flutter 官网下载 Flutter SDK,并按照平台特定的安装指南进行安装。

  3. 配置环境变量:

    • 在 Linux 和 macOS 系统中,编辑 ~/.bashrc 或 ~/.zshrc 文件,添加以下内容:
export PATH="$PATH:/path/to/flutter/bin

注意:在 Windows 系统中,打开环境变量设置,将 Flutter SDK 路径添加到 PATH 环境变量中。

鼠标右击选择【属性】->【系统信息】-> 高级系统设置

image.png

选择环境变量

image.png

选择环境变量->选择path

image.png

将flutter的bin的路径添加到path

image.png

  1. 安装 Flutter IDE:建议使用 VS Code 作为 Flutter 开发 IDE,并安装 Flutter 相关的插件。
  2. 安装其他必备工具:安装 Android Studio 和 Xcode,并配置好 Android SDK 和 iOS SDK。

安装完成后,在终端运行 flutter doctor 命令来检查 Flutter 和 Dart 的安装是否成功。

当开发环境搭建好后,我们就可以开启第一个 Flutter 项目了。在终端使用以下命令创建一个新的 Flutter 项目:

flutter create my_first_flutter_app
cd my_first_flutter_app 
flutter run

运行上述命令后,你将在默认浏览器中看到 Flutter 应用的预览页面,这标志着你正式踏上了 Flutter 开发的征程。

变量及常量

变量

变量是存储数据的容器,其值可以在程序运行过程中改变。在 Dart 里,使用 var 关键字来声明变量。

var

常量

常量是在程序运行过程中值不能被改变的量,Dart 中有两种常量声明方式

常量区别
final编译时不可修改
const运行时不可修改

代码演示如下:

void main() {
  // 变量 var
  var age = 20;
  age = 21;
  print(age);
  // 常量 const
  const number = 3.1415926;
  const length = number * 2 * 10;
  print(length);
  // 常量 final
  final time = DateTime.now();
  prnt(time);
}

数据类型

Int

整型类型 用于表示整数,取值范围为 -2^53 到 2^53,不能包含小数点。例如:int age = 25;

Double

浮点类型 表示 64 位(双精度)浮点数,用于处理带有小数点的数字。例如:double price = 9.99;

int 和 double 都是 num 类型的子类,num 类型支持的运算操作有 +-*/ 以及移位操作 >> 等,常用方法有 abs()ceil() 和 floor() 等

String

用于表示文本信息,可以使用单引号 ' 或双引号 " 来定义,也可以使用单引号或双引号的三引号来创建多行文字。

   String s1 = '单引号定义字符串';
   String s2 = "双引号定义字符串";
   String s3 = '''这是一个
   多行的
   文本''';
   String s4 = """这也是一个
   多行的
   文本""";

字符串还支持插值操作,类似于 Kotlin 字符串模板,例如:

int num = 10;
String message = "数字是 $num";
  1. Boolean 类型:只有两个值 truefalse,用于表示逻辑判断的结果。例如:bool isChecked = true;

集合类型

  1. List 类型:是一种有序容器,其中可以包含任何类型的元素,列表里的数据通常要求是同一类型,下标从 0 开始。
    • 字面量方式创建
    List<int> numbers = [1, 2, 3, 4, 5];
    
    • 构造方式创建
    List<int> list = List.empty(growable: true);
    list.add(1);
    
    • 扩展操作符
    var list1 = [1, 2, 3];
    var list2 = [0, ...list1];
    
  2. Map 类型:是一种无序容器,将 KeyValue 关联在一起,也就是键值对,Key 必须是唯一的。
    • 字面量方式创建
    Map<String, int> ageMap = {"John": 30, "Jane": 25};
    
    • 构造方式创建
    var weekMap = new Map();
    weekMap['Monday'] = '星期一';
    
  3. Set 类型:是一种无序容器,其中每个元素都是唯一的。
    Set<int> numbersSet = {1, 2, 3, 4, 5};
    numbersSet.add(6);
    

其他类型

  1. Dynamic 类型:可以存储任何类型的值,不限于上述类型。但是,在使用动态类型时需要更加小心,因为编译器无法检查类型错误。
    dynamic value = 10;
    value = "hello";
    
  2. Symbol 类型:在 Dart 中,Symbol 类型表示编译时常量标识符,通常用于反射。例如:const symbol = #mySymbol;

特殊值

在 Dart 中,使用 null 表示空值或不存在的值,当变量未被赋值时,默认值为 null。例如:

int? number;
print(number); // 输出: null

自定义组件

无状态组件 [StatelessWidget]

  • 无状态组件是不可变的,意味着它们的属性不能改变, 所有的值都是最终的。
  • 通常用于当你需要展示的UI不依赖于对象内部状态时。
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      title: "无状态组件",
      home:Scaffold(
        appBar: AppBar(
          title:Text("顶部区域"),
        ),
        body: Container(
          child: Center(
            child: Text("中部本区域"),
          ),
        ),
        bottomNavigationBar: Container(
          height: 80,
          child: Center(
            child: Text("底部区域"),
          ),
        )
      )
    );
  }
}

有状态组件 [StatefulWidget]

  • 有状态组件可以在其生命周期中改变状态。
  • 通常用于当UI可以在用户交互或其他因素影响下改变时
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
// 对外接受和定义最终参数 
class MainPage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _MainPage();
  }
}
// 负责管理数据 处理业务逻辑 渲染视图
class _MainPage extends State<MainPage>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      title: "有状态组件",
      home:Scaffold(
        appBar: AppBar(
          title:Text("顶部区域"),
        ),
        body: Container(
          child: Center(
            child: Text("中部本区域"),
          ),
        ),
        bottomNavigationBar: Container(
          height: 80,
          child: Center(
            child: Text("底部区域"),
          ),
        )
      )
    );
  }
}

使用Awesome Flutter Snippets用来创建有状态组件及无状态组件的快捷键

无状态组件:statelessW

有状态组件:statefulW

代码示例

以下是无状态组件代码示例

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(StateLessWeight());
}

class StateLessWeight extends StatelessWidget {
  const StateLessWeight({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: null,
    );
  }
}

以下部分是有状态组件代码示例

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(TestWeight());
}

class TestWeight extends StatefulWidget {
  TestWeight({Key? key}) : super(key: key);

  @override
  _TestWeightState createState() => _TestWeightState();
}

class _TestWeightState extends State<TestWeight> {
  @override
  Widget build(BuildContext context) {
    return Container(
       child: null,
    );
  }
}

组件的生命周期

无状态组件

存在唯一阶段:build,当组件被创建或者父组件的状态发生变化,导致下面的组件发生变化并需要重新构建时,此方法会被调用

代码示例

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print("无状态组件的执行");
    return MaterialApp(
      home: Scaffold(
        body: Container(
          child: Center(
            child: Text("无状态"),
          ),
        ),
      ),
    );
  }
}

有状态组件

build阶段的生命周期

stateDiagram-v2

createState --> initState
initState --> didChangeDependencies
didChangeDependencies --> build

组件创建 --> 组件初始化 
组件初始化 --> 更改依赖项 
更改依赖项 --> 构建 

update阶段的生命周期

stateDiagram-v2

父组件重建_配置变更 --> didUpdateWidget
didUpdateWidget --> build

销毁阶段的生命周期

stateDiagram-v2

组件移除 --> deactivate
deactivate --> dispose

生命周期函数

生命周期阶段函数名调用时机与核心任务
创建阶段createState()Widget初始化调用,创建State对象
initState()state对象插入Widget立即执行,仅执行一次
didChangeDependencies()initState后立刻执行,当所依赖的inheritedWidget更新时调用,可能多次
构建与更新阶段build()创建UI方法,初始化或更新后多次调用
didupdateWidget()父级组件传入新配置时调用,用于比较新旧配置
销毁阶段deactiveate()当State对象从树中暂时移除时调用
dispose()释放资源

执行一次函数: createState initState dispose

lnheritedWidget

专门用于在widget树中自定向下高效进行信息共享,其中顶部组件负责提供数据 子级数据负责进行数据的获取。

事件-点击事件

GestureDetector 用户与应用程序交互时触发的各种动作 特征:屏幕的滑动 点击及触摸

常用方法及说明

常用方法说明
OnTap()单击事件
OnDoubleTap()屏幕双击事件

代码演示

单击事件

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "点击事件演示",
      home: Scaffold(
        appBar: AppBar(
          title: Text("标题区域"),
        ),
        body: Container(
          child: Center(
            child: GestureDetector(
              child: Text("测试中部区域"),
              // 单击事件
              onTap: (){
                print("测试区域");
              },
            )
          )
        ),
        bottomNavigationBar: Container(
          height: 80,
          child: Center(
            child: Text("底部区域"),
          ),
        ),
      ),
    );
  }
}

双击事件

此段代码仅展示上段代码修改区域

// 单击事件
// onTap: (){
//   print("测试区域");
// },
// 双击事件
onDoubleTap: (){
    print("双击该区域");
}

长按事件

// 单击事件
// onTap: (){
//   print("测试区域");
// },
// 双击事件
// onDoubleTap: (){
//   print("双击该区域");
// },
onLongPress: (){
  print("长按该区域");
},

组件点击事件

常用方法及说明

flutter提供多种方式为组件添加点击交互

组件类别核心组件组件说明
专用按钮组件ElevatedButton、TextButton、OutlineButton、FloatingActionButton内置点击动画和样式,通过onPressed参数处理点击逻辑
视觉反馈组件lnkwell提供点击事件(onTap) 有MaterialDesign风格的水纹扩散效果
其他交互组件IconButton、Switch、checkbox具有特定交互功能的交互式控件,点击事件(onPressed)
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}
class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "点击事件演示",
      home: Scaffold(
        appBar: AppBar(
          title: Text("标题区域"),
        ),
        body: Container(
          child: Center(
            child: GestureDetector(
              // TextButton组件
              child: TextButton(
                onPressed: (){
                  print("TextButton测试");
                }, 
              child: Text("按钮测试")
              ),
            )
          )
        ),
        bottomNavigationBar: Container(
          height: 80,
          child: Center(
            child: Text("底部区域"),
          ),
        ),
      ),
    );
  }
}

OutlinedButton代码演示

body: Container(
  child: Center(
      child: GestureDetector(
          child: OutlinedButton(onPressed: (){
               print("数据");
          }, child: Text("数据"))
      ),
   )
),

状态更新setState

定义

在 Flutter 里,setState 是 State 类的一个方法,其作用是通知 Flutter 框架当前组件的状态已经发生了变化,需要重新构建 UI。当调用 setState 方法时,Flutter 会调用 State 对象的 build 方法来重新生成 UI 界面。其定义形式通常如下

void setState(VoidCallback fn) { 
// 内部实现逻辑,通知框架状态改变 // 然后触发 build 方法重新构建 UI
}

这里的 fn 是一个回调函数,在这个回调函数中可以修改组件的状态变量。

原理

  1. 状态改变触发:当在 State 类中调用 setState 方法时,需要传入一个回调函数。在这个回调函数内部对组件的状态变量进行修改。例如:
class _MyStatefulWidgetState extends State<MyStatefulWidget> { 
    int counter = 0;
    void _incrementCounter() { 
        setState(() { counter++; }); 
    } 
    @override 
    Widget build(BuildContext context) { 
        return Column( children: [ Text('Counter: $counter'), ElevatedButton( onPressed: _incrementCounter, child: Text('Increment'), ), ], ); 
    } 
}

在上述代码中,点击 ElevatedButton 会调用 _incrementCounter 方法,在该方法里调用了 setState 并在其回调函数中修改了 counter 的值。

  1. 标记组件为脏组件:调用 setState 后,Flutter 框架会将当前的 State 对象标记为“脏”(dirty)。这意味着该组件的状态已经改变,需要重新构建 UI。
  2. 重新构建 UI:在后续的帧构建过程中,Flutter 框架会遍历所有标记为“脏”的组件,并依次调用它们的 build 方法。在 build 方法中,会使用更新后的状态变量来创建新的 Widget 树。
  3. 渲染更新:新的 Widget 树会与旧的 Widget 树进行比较(通过 Key 和 runtimeType 等),找出有差异的部分,然后只更新那些有变化的部分到屏幕上,从而实现 UI 的更新。

总结来说,setState 的原理就是通过标记组件状态改变,触发 build 方法重新构建 UI,再通过比较新旧 Widget 树来高效地更新屏幕显示。

代码演示

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(mainPage());
}

// 使用无状态组件
class mainPage extends StatefulWidget {
  mainPage({Key? key}) : super(key: key);

  @override
  _mainPageState createState() => _mainPageState();
}

class _mainPageState extends State<mainPage> {
  // 数据定义
  int count = 3;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            children: [
              TextButton(onPressed: (){
                if(count==0){
                  count=0;
                }else{
                  setState(() {
                    count-=1;
                  });
                }
                
              }, child: Text("减")),
              Text(count.toString()),
              TextButton(onPressed: (){
                // UI 数据更新
                setState(() {
                  count+=1;
                });
              }, child: Text("加")),
            ],
          ),
        ),
      ),
    );
  }
}