Dart 笔记地址:Dart

配置 Flutter 环境地址:选择你的开发平台,开始使用

创建 & 运行

创建项目

创建 Flutter 有两种方式:

  1. 使用 VSCode 控制台,键盘上输入快捷键 Ctrl + Shift + P 或者 Command + Shift + P

输入 Flutter: New Project,输入项目名即可

  1. 控制台输入:flutter create <project-name>即可创建

运行项目:

使用 flutter run运行项目

调试:

在控制台直接输入:

  • r:重新载入,加载更新

  • p:显示网格,用于查看布局

  • o:切换android和ios的预览模式。

  • q:退出预览模式

目录结构

其中最重要的是如下文件夹,其他不必理会:

文件夹

作用

android

android 平台相关代码

ios

ios 平台相关代码

linux

Linux 平台相关代码

macos

macos 平台相关代码

web

web 相关代码

lib

flutter 编写代码,我们写的代码就在这

test

用于存放测试代码

pubspec.yaml

配置文件,一般放一些第三方库依赖

analysis_options.yaml

分析 dart 语法的文件,出现错误可以删除

入口文件

在 Flutter/lib/main.dart 文件就是整个项目的入口文件。

main.dart 中的

void main() {
  runApp(MyApp())
}	
// 也可以简写
void main() => runApp(MyApp())

组件 Widget

Widget 概述

Flutter 中,一切皆是组件,每个组件都是一个类。

Widget 是 Flutter 应用用户界面的构建块。

Widget 根据组合形成层次结构,每个 widget 都嵌套在其父级中,并且可以从父级接收上下文。

Widget 其实就是一个构成用户界面的基本单位

  • 有布局小组件,比如 Padding, Alignment, Row, Column, Grid等,这类布局组件没有其视觉表达,其唯一的目的就是控制内部小组件的布局方面。

  • 还有一系列实用组件,这些组件有可视化表达,比如:Text, Icon, Image

  • 每一个 Widget 都有一个 build 方法,我们需要覆盖 Widget 方法,并且让其返回一个 Widget

Widgets

关于 Flutter,你经常会听到 “一切都是一个小部件”。Widget 是 Flutter 应用用户界面的构建块,每个 widget 都是用户界面部分的不可变声明。

Widget 根据组合形成层次结构。每个 widget 都嵌套在其父级中,并且可以从父级接收上下文。此结构一直延续到根 widget,如这个简单的示例所示:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Root widget
      home: Scaffold(
        appBar: AppBar(
          title: const Text('My Home Page'),
        ),
        body: Center(
          child: Builder(
            builder: (context) {
              return Column(
                children: [
                  const Text('Hello, World!'),
                  const SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: () {
                      print('Click!');
                    },
                    child: const Text('A button'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

Center 组件

import 'package:flutter/material.dart';

void main() {
  runApp(const Center(
    child: Text(
        'Hello Flutter',
        textDirection: TextDirection.ltr,
      ),
  ));
}

Row 水平布局 / Column 垂直布局

使用 Row 组件进行水平排列, 使用 Column 进行垂直布局。

分为主轴和交叉轴,主轴使用 mainAxisAlignment 控制,交叉轴使用 crossAxisAlignment 控制。

布局属性:

  • mainAxisAlignment:MainAxisAlignment.spaceEvenly 类似 Flex 布局中的主轴对齐方式

  • crossAxisAlignment:CrossAxisAlignment.center 类似 Flex 布局中的交叉轴对齐方式

Row:

Widget build(BuildContext context) {
  return Row(
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}


Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}


Column:

Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      Column(
        children: [
          BorderedImage(),
          Text('Dash 1'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 2'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 3'),
        ],
      ),
    ],
  );
}


Widget build(BuildContext context) {
  return Column(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ]
  ),
}


Expanded 类 Flex 布局

当布局太大而无法容纳设备时,受影响的边缘会出现黄色和黑色的条纹图案。在此示例中,视区宽 400 像素,每个子对象宽 150 像素。

可以使用 Expanded Widget 调整 Widget 的大小以适合行或列。要修复前面的示例,即图像行对于其渲染框来说太宽,请使用 Expanded 小部件包装每个图像。

Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}

Expanded widget 还可以指示 widget 相对于其同级应占用多少空间。例如,您可能希望一个 Widget 占用的空间是其同级的两倍。为此,请使用 Expanded widgets flex 属性,这是一个确定 widget 的 flex 因子的整数。默认弯曲系数为 1。以下代码将中间图像的弯曲系数设置为 2:

🤯 其实就是类似于 Flex 布局,可以控制换这个子项要占用几份

Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        flex: 2,
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}

MaterialApp 结构组件

MaterialApp 是 Flutter 框架中一个非常重要的组件,它为应用提供了一个遵循 Material Design 规范的基本结构和主题。MaterialApp 通常是 Flutter 应用程序的根部件,负责管理路由、主题、语言环境等。

🌟 什么是 Material Design 规范?

Material Design 是 Google 设计的, 一个用于 App 设计的全面规范。它主要面向移动应用程序(尤其是 Android 应用),但也适用于 Web 和其他平台。这个设计语言旨在提供一致的用户体验,并通过一套统一的视觉、运动和互动原则来指导设计师和开发者。

Material Design 规范

  1. 材料和表面
    材料的隐喻:Material Design 使用的“材料”是一种称为“纸张”的数字化材质,它具有物理世界中的属性,如厚度、边缘和阴影。
    阴影和深度:通过阴影来表达界面的层次结构和交互性。

  2. 布局
    栅格系统:使用灵活的栅格系统进行布局设计,以确保应用在不同设备上的一致性。
    响应式设计:内容和控件能够根据屏幕大小和方向自动调整。

  3. 色彩
    调色板:提供丰富多样的颜色选项,并包含主色、强调色和文本颜色。
    色彩对比:确保足够的对比度,提高可读性和可访问性。

  4. 排版
    字体:使用清晰易读的排版,推荐使用 Google 的 Roboto 字体。
    层次结构:通过不同的字体大小、字重和颜色来创建视觉层次。

  5. 图标
    一致性:图标应简单、直观并具有一致性。
    可扩展性:设计成在不同尺寸和分辨率下都能很好地呈现。

  6. 动画
    自然动效:动画应模仿现实世界的物理效果,如加速、减速等。
    意义明确:每个动画都应该有助于增强用户体验,而不是分散注意力。

  7. 组件
    卡片:用于显示相关信息和操作。
    按钮:主按钮、副按钮及浮动按钮用于执行操作。
    输入框:提供用户输入的方式,包括文本框、选择器等。

  8. 模式
    导航:通过抽屉菜单、标签页、底部导航栏等实现有效导航。
    反馈:及时向用户提供操作结果的反馈信息。

Material Design 强调统一性、视觉美感和用户体验,使得应用程序在各个平台上都能提供一致的用户体验。更多详细信息可以参考 Material Design

主要属性:

  • home: 指定应用的首页小部件。当应用启动时,这个小部件会被显示。通常是一个 Scaffold。

home: MyHomePage(),
  • routes: 定义应用的路由表,是一个 Map<String, WidgetBuilder>,用于将字符串路径映射到不同的小部件构建器。

Scaffold 框架组件

Scaffold 是 Flutter 中一个常用的布局组件,它为 Material Design 应用提供了基本的视觉布局结构。Scaffold 可以帮助你快速搭建应用的基本视觉框架,比如提供导航栏、抽屉、底部导航栏等。

主要属性

  • appBar: 用于设置顶部的应用栏(App Bar),通常包含标题、导航图标和操作按钮。由 AppBar 小部件实现。

appBar: AppBar(
  title: Text('Home Page'),
),
  • body: Scaffold 的主体部分,通常用于显示主要内容。你可以在这里放置各种类型的小部件,比如 Column, Row, ListView 等。

body: Center(
  child: Text('Hello, World!'),
),
  • drawer: 为应用添加一个左侧抽屉菜单,可以通过滑动或点击导航图标来打开。通常用于导航。

drawer: Drawer(
  child: ListView(
    children: <Widget>[
      DrawerHeader(child: Text('Header')),
      ListTile(title: Text('Item 1')),
      ListTile(title: Text('Item 2')),
    ],
  ),
),
  • bottomNavigationBar: 设置底部导航栏,典型应用是通过 BottomNavigationBar 实现分页导航。

bottomNavigationBar: BottomNavigationBar(
  items: [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
    BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
  ],
),
  • floatingActionButton: 一个悬浮在 Scaffold 上的浮动按钮,通常用于实现一个最重要的用户动作,通过 FloatingActionButton 实现。

floatingActionButton: FloatingActionButton(
  onPressed: () {
    // Action to perform
  },
  child: Icon(Icons.add),
),
  • bottomSheet: 用于显示固定在屏幕底部的面板。

bottomSheet: BottomSheet(
  onClosing: () {},
  builder: (context) => Container(
    height: 100,
    color: Colors.blue,
    child: Center(child: Text('Bottom Sheet')),
  ),
),

Container 容器布局组件

Container 是一个方便的 widget,它可以负责布局、绘制、定位和调整大小。

Widget build(BuildContext context) {
  return Container(
    padding: EdgeInsets.all(16.0),
    child: BorderedImage(),
  );
}

感觉有点像是 div

ListView 滚动组件

ListView是一个列状组件,当其内容长于渲染框时,会自动提供滚动,类似 React Native 中的 ScrollView。使用 ListView的最基本方法与使用 Column 和 Row 差不多。但不同的是,ListView 要求其子项占用交叉轴上的所有可用空间。

Widget build(BuildContext context) {
  return ListView(
    children: const [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

如果 child 有具体的内容,则使用上方方式,如果具有未知或非常大的数量的列表项时,最好使用 ListView.builder构造函数。

final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}

LayoutBuilder(Adaptive layouts 自适应布局)

我们可能使用 Flutter 构建移动、平板、桌面、web 程序,根据屏幕的不同大小做出不同行为。

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      if (constraints.maxWidth <= 600) {
        return _MobileLayout();
      } else {
        return _DesktopLayout();
      }
    },
  );
}

GestureDetector 点击事件组件

可以使用 GestureDetector 组件绑定 onTap 事件实现点击事件,无水波纹

GestureDetector(
  onTap: () {
    print(item);
    Navigator.push(context, MaterialPageRoute(builder: (context) {
      return WebViewPage(title: item['title']);
    }));
  },
  child: ......
)

InkWell 点击事件组件(水波纹)

可以使用 InkWell 组件绑定 onTap 事件实现点击事件,带水波纹

InkWell(
  onTap: () {
    print(item);
    Navigator.push(context, MaterialPageRoute(builder: (context) {
      return WebViewPage(title: item['title']);
    }));
  },
  child: ......
)

使用 Navigator 组件进行跳转,需要返回一个 Widget

Navigator.push(context, MaterialPageRoute(builder: (context) {
  return WebViewPage(title: item['title']);
}));

路由传参数的话,在返回的 Widget 中写好属性,并在构造函数中指定即可

import 'package:flutter/material.dart';

class WebViewPage extends StatefulWidget {
  final String title;
  const WebViewPage({super.key, required this.title});

  @override
  State<StatefulWidget> createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 这里可以使用
      appBar: AppBar(title: Text(widget.title)),
      body: SafeArea(
        child: Container(
          child: InkWell(
            onTap: () {
              Navigator.pop(context);
            },
            child: Container(),
          ),
        ),
      ),
    );
  }
}

状态管理

StatelessWidget 和 StatefulWidget 状态组件

在 Flutter 中,StatelessWidget 和 StatefulWidget 是用于创建用户界面的两种基本类型的组件(小部件)。它们之间的主要区别在于状态(state)的可变性。

StatelessWidget 无状态组件

  • 不可变:一旦构建完成,其配置和属性就不会改变。

  • 用途:适用于展示静态内容或简单的用户界面元素,例如图标、文本、按钮等,这些内容在应用生命周期中不需要更新。

  • 实现:当想要创建一个不随状态变化而变化的组件时,继承自 StatelessWidget 并实现 build 方法。

class MyStatelessWidget extends StatelessWidget {
  final String title;

  MyStatelessWidget({required this.title});

  @override
  Widget build(BuildContext context) {
    return Text(title);
  }
}

StatefulWidget 有状态组件

  • 可变:在其生命周期内可以保持状态,并且这些状态可以随时间发生变化。

  • 用途:适合需要根据用户交互或其他因素动态更新UI的组件。例如,复选框、动画、表单输入等。

  • 实现:

  • 需要两个类:一个是继承自 StatefulWidget 的类,另一个是继承自 State 的类。前者负责创建状态对象,而后者负责维护状态并实现 build 方法。

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

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'),
        ),
      ],
    );
  }
}

父子组件传值

功能实现

滑动切换 BottomBar

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late PageController _pageController;
  int _currentIndex = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController();  // 正确初始化
  }

  @override
  void dispose() {
    _pageController.dispose();  // 释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        onPageChanged: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        children: const <Widget>[
          Center(child: Text('Home Page')),
          Center(child: Text('Search Page')),
          Center(child: Text('Settings Page')),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          _pageController.animateToPage(
            index,
            duration: Duration(milliseconds: 300),
            curve: Curves.easeInOut,
          );
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'Settings',
          ),
        ],
      ),
    );
  }
}

瀑布流

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/chen-bo-bo/fe/se39p4druprn7yz4

踩坑日记

iOS 运行失败

出现问题:

参考:https://stackoverflow.com/questions/79118572/xcode-16-and-ios-18-project-not-compiling