flutter学习笔记

01 flutter3 环境搭建

最新版的flutter在进行环境搭建的时候需要额外安装Visual Studio,以对windows桌面版开发功能进行支持。可以在命令行中输入以下配置跳过这一步骤:

flutter config --no-enable-windows-desktop

02 flutter简要介绍

在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)
widget的主要工作是提供一个build()方法来描述如何根据其他较低级别的widget来显示自己。
使用外部包的方法:在pubspec.yaml中,将english_words(添加到依赖项列表,如下面高亮显示的

dependencies:
  flutter:
    sdk: flutter

  english_words: ^3.1.5

注意格式的对齐,然后点击上方Packages get,这会将依赖包安装到项目。然后再引入即可。

Flutter 从上到下可以分为三层:框架层、引擎层和嵌入层

Flutter 框架的的处理流程是这样的:

根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。
真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。
三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。

json实体类自动生成工具:https://caijinglong.github.io/json2dart/index_ch.html
终端命令:

flutter packages pub run build_runner build

Flutter布局基础——Stack层叠布局

层叠布局适用于子视图叠放一起,且位置能够相对于父视图边界确认的情况。
比如,可用于图片上加文字,按钮上加渐变阴影等等。
Stack Widget的子视图要么是positioned,要么是non-positionedPositioned子视图是指使用Positionedwidget包括起来的子视图,通过设置相对于Stacktopbottomleftright属性来确认自身位置,其中至少要有一个不为空。

Stack Widget的大小取决于所有non-positioned的子视图。non-positioned的子视图的位置根据alignment属性确定,(当alignmentleft-to-right时,子视图默认从左上角开始;当aligmentright-to-left时,子视图从右上角开始;)。

Flutter中使用StackPositioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

Dart——数据类型:键值对Map

map 是一个无序的 key-value (键值对)集合,其中键和值都可以是任何类型的对象。每个 键 只能出现一次但是 值 可以重复出现多次。 DartMap 提供了 Map 字面量以及 Map 类型两种形式的 Map
使用 Map 的构造器创建 Map

var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

向现有的 Map 中添加键值对

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';

从一个 Map 中获取一个值

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');
var nobleGases = {54: 'xenon'};

// Retrieve a value with a key.
assert(nobleGases[54] == 'xenon');

// Check whether a map contains a key.
assert(nobleGases.containsKey(54));

// Remove a key and its value.
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));

可以从一个 map 中检索出所有的 key 或所有的 value:

var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu']
};

// Get all the keys as an unordered collection
// (an Iterable).
var keys = hawaiianBeaches.keys;

assert(keys.length == 3);
assert(Set.from(keys).contains('Oahu'));

// Get all the values as an unordered collection
// (an Iterable of Lists).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));

使用 .length 可以获取 Map 中键值对的数量
在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量

Dart——数据类型:泛型

  • 适当地指定泛型可以更好地帮助代码生成。
  • 使用泛型可以减少代码重复。

一个只能包含 String 类型的数组,可以将该数组声明为 List(读作“字符串类型的 list”)

flutter 项目中使用Hive进行数据本地存储

hive是一个高性能和轻量级的nosql数据库,使用键值对形式进行存储。

使用Hive的四个简单步骤:

Step 1 Setup Hive

  • 在yaml文件下添加依赖
dependencies:
  flutter:
    sdk: flutter
  hive: ^2.0.4
  hive_flutter: ^1.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  hive_generator: ^1.0.0
  build_runner: ^1.12.2 
  • main.dart
// 1.
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {

// 2.
  WidgetsFlutterBinding.ensureInitialized(); 
  // 初始化hive
  await Hive.initFlutter();
  
  runApp(MyApp());
}

Step 2 Create Hive Model

将想要进行本地存储的 model classes转化为 hive models.

  • 创建一个模型类 lib/model/transaction.dart
class Transaction {
  late String name;

  late DateTime createdData;

  late bool isExpense = true;

  late double amount;
}
 

如果你想在你的配置单元数据库存储中进行存储和加载你的模型类,那么需要遵循三个步骤:

  1. 修改模型类
// 1.
import 'package:hive/hive.dart';
// 这个文件是稍后生成的模拟类适配器
part 'transaction.g.dart';                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          

// 2. 注释模型类,并提供所有模型类之间的唯一数字
@HiveType(typeId: 0)
// 从单元对象扩展你的模型类,从而使用HiveObject里面的所有方法
class Transaction extends HiveObject {

// 3. 注释模型类中要保留在hive数据库中的所有字段
  @HiveField(0)
  late String name;

  @HiveField(1)
  late DateTime createdData;

  @HiveField(2)
  late bool isExpense = true;

  @HiveField(3)
  late double amount;
}
 
  1. 基于模型类生成一个模型类适配器(Model Adapter)
    进入终端在项目根目录运行命令:flutter packages pub run build_runner build 。
  2. 在配置单元系统(hive system)中注册这个模型类适配器 main.dart:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

// 1.
import './model/transaction.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化hive
  await Hive.initFlutter();
  
  // 2.
  Hive.registerAdapter(TransactionAdapter());

  runApp(MyApp());
}

Step 3 Use CRUD Operations

配置增删改查操作

先来了解hive存储数据的原理:

  • hive将数据以键值对的形式存储在boxes中,可以把一个box想象成一个map,box可以接受任何类型的对象作为值。
    flutter学习笔记_第1张图片
  • 所有东西都可以放在一个box中,或者可以创建其他盒子。
    flutter学习笔记_第2张图片
  • 内部工作原理是:对于每个box,都会在你的文件存储本地创建一个不同的文件。

使用我们的box

  1. main.dart:
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化hive
  await Hive.initFlutter();
  Hive.registerAdapter(TransactionAdapter());
  
  // 1.打开指定box
  await Hive.openBox<Transaction>("transactions");

  runApp(MyApp());
}
  1. transaction_page.dart:
class _TransactionPageState extends State<TransactionPage> {
  final List<Transaction> transactions = [];

  // 定义hive相关方法
  @override
  void dispose() {
    // 1. 关闭所有的 hive boxes
    Hive.close();
    // 2. 关闭名为 transactions 的盒子
    Hive.box("transactions").close();

    super.dispose();
  }

Step 4 Connect UI To Hive

使UI监听到数据的修改并重新渲染页面
使用一些监听的方法:ValueListenableBuilder() TextEditingController()等。

完整案例:

(JohannesMilke /hive_database_example)

  • main.dart:
import 'package:flutter/material.dart';
// import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'model/transaction.dart';
import 'pages/transaction_page.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化hive
  await Hive.initFlutter();
  Hive.registerAdapter(TransactionAdapter());
  await Hive.openBox<Transaction>("transactions");

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  static const String title = "Hive Expense App";

  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        debugShowCheckedModeBanner: false,
        title: title,
        theme: ThemeData(primarySwatch: Colors.indigo),
        home: const TransactionPage(),
      );
}
  • model/transaction.dart:
import 'package:hive/hive.dart';

part 'transaction.g.dart';

@HiveType(typeId: 0)
class Transaction extends HiveObject {

  @HiveField(0)
  late String name;

  @HiveField(1)
  late DateTime createdData;

  @HiveField(2)
  late bool isExpense = true;

  @HiveField(3)
  late double amount;
}
 
  • pages/transaction_page.dart:
import 'package:flutter/material.dart';
import 'package:myapp/model/transaction.dart';
// import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
// 转化时间格式的工具
import 'package:intl/intl.dart';
import 'package:myapp/Widget/transaction_dialog.dart';

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

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

class _TransactionPageState extends State<TransactionPage> {
  // 定义hive相关方法
  @override
  // dispose方法(生命周期 ):在State对象从树中被永久移除时调用
  void dispose() {
    // 关闭所有的 hive boxes
    Hive.close();
    //  关闭名为 transactions 的盒子
    // Hive.box("transactions").close();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text("Hive Expense Tracker"),
          centerTitle: true,
        ),
        // 在这里设置监听
        body: ValueListenableBuilder<Box<Transaction>>(
          valueListenable: Boxes.getTransactions().listenable(),
          builder: (context, box, _) {
            // 得到的值要进行类型转换
            final transactions = box.values.toList().cast<Transaction>();
            // 返回一个构建内容的组件
            return buildContent(transactions);
          },
        ),
        floatingActionButton: FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () => showDialog(
                context: context,
                builder: (context) => const TransactionDialog(
                      onClickedDone: addTransaction,
                    ))),
      );
}

// 传入 Transaction 类型的 transactions
Widget buildContent(List<Transaction> transactions) {
  // 内容如果为空显示占位符
  if (transactions.isEmpty) {
    return const Center(
      child: Text(
        "No expenses yet!",
        style: TextStyle(fontSize: 24),
      ),
    );
  } else {
    // 调用math fold方法,从初始值0开始,在过程中发生改变
    final netExpense = transactions.fold<double>(
      0,
      (previousValue, transaction) => transaction.isExpense
          ? previousValue - transaction.amount
          : previousValue + transaction.amount,
    );
    final newExpenseString = "\$${netExpense.toStringAsFixed(2)}"; // 四舍五入
    final color = netExpense > 0 ? Colors.green : Colors.red;

    return Column(
      children: [
        const SizedBox(height: 24),
        Text(
          "Net Expense: $newExpenseString",
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 20,
            color: color,
          ),
        ),
        const SizedBox(height: 24),
        Expanded(
          child: ListView.builder(
              padding: const EdgeInsets.all(8),
              itemCount: transactions.length,
              itemBuilder: (BuildContext context, int index) {
                final transaction = transactions[index];

                //传递两个参数
                return buildTransaction(context, transaction);
              }),
        ),
      ],
    );
  }
}

Widget buildTransaction(
  BuildContext context,
  Transaction transaction,
) {
  final color = transaction.isExpense ? Colors.red : Colors.green;
  final date = DateFormat.yMMMd().format(transaction.createdData);
  final amount = "\$" + transaction.amount.toStringAsFixed(2);

  return Card(
    color: Colors.white,
    // 可以展开的标题控件
    child: ExpansionTile(
      tilePadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
      title: Text(
        transaction.name,
        maxLines: 2,
        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
      ),
      subtitle: Text(date),
      trailing: Text(
        amount,
        style:
            TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 16),
      ),
      children: [
        buildButtons(context, transaction),
      ],
    ),
  );
}

Widget buildButtons(BuildContext context, Transaction transaction) => Row(
      children: [
        Expanded(
          child: TextButton.icon(
          label: const Text("Edit"),
          icon: const Icon(Icons.edit),
          onPressed: () => Navigator.of(context).push(
            MaterialPageRoute(
              builder:(context) => TransactionDialog(
                transaction:transaction,
                onClickedDone:(name, amount, isExpense) => 
                editTransaction(transaction,name,amount,isExpense),
              ),),
          ),),),
          Expanded(
            child: TextButton.icon(
              label: const Text("Delete"),
              icon: const Icon(Icons.delete),
              onPressed: () => deleteTransaction(transaction),
            )
          ),
      ],
    );

// 添加的方法
  Future addTransaction(String name, double amount, bool isExpense) async {
    // 创建一个新的 Transaction 对象
    final transaction = Transaction()
      ..name = name
      ..createdData = DateTime.now()
      ..amount = amount
      ..isExpense = isExpense;

    // 首先获取box 创建一个新类:Boxes
    final box = Boxes.getTransactions();
    // 调用添加方法
    box.add(transaction);
    // box.put("mykey", transaction);
  }

// 更新的方法
  void editTransaction(
    Transaction transaction,
    String name,
    double amount,
    bool isExpense,
  ) {
    transaction.name = name;
    transaction.amount = amount;
    transaction.isExpense = isExpense;
// 调用保存的方法
    transaction.save();
  }

// 删除的方法
  void deleteTransaction(Transaction transaction) {
      // 调用删除的方法
      transaction.delete();
  }

// 监听对象
class Boxes {
  static Box<Transaction> getTransactions() =>
      // 调用hivebox来访问box
      Hive.box<Transaction>("transactions");
}

  • Widget/transaction_dialog.dart:
import 'package:flutter/material.dart';
import 'package:myapp/model/transaction.dart';

class TransactionDialog extends StatefulWidget {
  final Transaction? transaction;
  final Function(String name, double amount, bool isExpense) onClickedDone;

  const TransactionDialog(
      {Key? key, this.transaction, required this.onClickedDone})
      : super(key: key);

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

class _TransactionDialogState extends State<TransactionDialog> {
  // 局部更新:GlobalKey 可以获取到对应的 Widget 的 State 对象
  final formKey = GlobalKey<FormState>();
  // TextEditingController:TextField控制器,
  // 用来操作TextField,为绑定的输入框预设内容;获取输入的内容;监听文字输入变化与焦点变化
  final nameController = TextEditingController();
  final amountController = TextEditingController();

  bool isExpense = true;

  @override
  // 页面初始化
  void initState() {
    super.initState();

    if (widget.transaction != null) {
      final transaction = widget.transaction!; // 做后缀的!会让左侧的表达式转成对应的非空类型

      nameController.text = transaction.name;
      amountController.text = transaction.amount.toString();
      isExpense = transaction.isExpense;
    }
  }

  @override
  // state被销毁
  void dispose() {
    nameController.dispose();
    amountController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final isEditing = widget.transaction != null;
    final title = isEditing ? "Edit Transaction" : "Add Transaction";

    return AlertDialog(
      title: Text(title),
      content: Form(
        key: formKey,
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const SizedBox(
                height: 8,
              ),
              buildName(),
              const SizedBox(
                height: 8,
              ),
              buildAmount(),
              const SizedBox(
                height: 8,
              ),
              buildRadioButtons(),
            ],
          ),
        ),
      ),
      actions: [
        buildCancelButton(context),
        buildAddButton(context, isEditing: isEditing),
      ],
    );
  }

  Widget buildName() => TextFormField(
        controller: nameController,
        decoration: const InputDecoration(
          border: OutlineInputBorder(),
          hintText: "Enter Name",
        ),
        validator: (name) =>
            name != null && name.isEmpty ? "Enter a name" : null,
      );

  Widget buildAmount() => TextFormField(
        controller: amountController,
        decoration: const InputDecoration(
          border: OutlineInputBorder(),
          hintText: "Enter Amount",
        ),
        keyboardType: TextInputType.number,
        validator: (amount) => amount != null && double.tryParse(amount) == null
            ? "Enter a valid number"
            : null,
      );

  Widget buildRadioButtons() => Column(
        children: [
          RadioListTile<bool>(
            title: const Text("Expense"),
            value: true,
            groupValue: isExpense,
            onChanged: (value) => setState(() {
              isExpense = value!;
            }),
          ),
          RadioListTile<bool>(
            title: const Text("Income"),
            value: false,
            groupValue: isExpense,
            onChanged: (value) => setState(() {
              isExpense = value!;
            }),
          ),
        ],
      );

  Widget buildCancelButton(BuildContext context) => TextButton(
        child: const Text("Cancel"),
        onPressed: () => Navigator.of(context).pop(),
      );

  Widget buildAddButton(BuildContext context, {required bool isEditing}) {
    final text = isEditing ? "Save" : "Add";

    return TextButton(
      child: Text(text),
      onPressed: () async {
        final isValid = formKey.currentState!.validate();

        if (isValid) {
          final name = nameController.text;
          final amount = double.tryParse(amountController.text) ??
              0; //如果等号后面的值为 null 则赋值为0

          widget.onClickedDone(name, amount, isExpense);

          Navigator.of(context).pop();
        }
      },
    );
  }
}

  • pubspec.yaml:
dependencies:
  flutter:
    sdk: flutter
  hive: ^2.0.4
  hive_flutter: ^1.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  hive_generator: ^1.0.0
  build_runner: ^1.12.2 
  intl: ^0.17.0

效果图:
flutter学习笔记_第3张图片
flutter学习笔记_第4张图片
flutter学习笔记_第5张图片

从本地&网络加载json数据

本地加载

  • 准备 assets/users.json 数据,并将其在yaml文件中加载
[
    {
      "username": "Emma Spoon",
      "email": "[email protected]",
      "urlAvatar": "https://images.unsplash.com/photo-1604426633861-11b2faead63c?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=700&q=80"
    },
    ...
]
  • 编写对应的model文件 user.dart:
class User {
  final String username;
  final String email;
  final String urlAvatar;

  // 创建构造函数并将所有字段放在里面
  const User({
    required this.username,
    required this.email,
    required this.urlAvatar,
  });

  // 创建 formJSON 方法:此方法返回一个用户,获取JSON的所有字段
  static User fromJSon(json) => User(
      username: json["username"],
      email: json["email"],
      urlAvatar: json["urlAvatar"]);
}

  • 编写调用json数据的api users_api.dart:
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_application_demo/model/user.dart';
// import 'package:sky_engine/_http/http.dart' as http;

class UsersApi {
  //  获取来自 user json文件的列表
  static Future<List<User>> getUsersLocally(BuildContext context) async {
    // 加载json文件
    final AssetBundle = DefaultAssetBundle.of(context);
    // 将数据作为一个字符串加载
    final data = await AssetBundle.loadString("assets/users.json");
    // 同时把它转化为json对象
    final body = json.decode(data);

    // 将json中的数据映射到用户对象上
    return body.map<User>(
      // 传入用户对象,从定义的json方法中创建
      User.fromJSon
    ).toList();// 获取用户列表
  }
}
  • 编写 user_local_page.dart:
import 'package:flutter/material.dart';
import 'package:flutter_application_demo/api/users_api.dart';
import 'package:flutter_application_demo/model/user.dart';

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

  @override
  Widget build(BuildContext context) => Scaffold(
        // FutureBuilder 时一个将异步操作和异步UI更新结合在一起的类,
        // 通过它我们可以将网络请求,数据库读取等的结果更新在页面上
        body: FutureBuilder<List<User>>(
          // 调用 UsersApi 获取本地用户信息

          /* future对象表示此构建器当前连接的异步计算 */
          future: UsersApi.getUsersLocally(context), // 传入一个上下文 获取json对象列表
          // 得到了数据之后将其构建为小组件

          /* builder : AsyncWidgetBuilder 类型的回调函数,是一个基于异步交互构建widget的函数
            它接受两个参数,BuildContext context 与 AsyncSnapshot snapshot 
            它返回一个 widget */
          builder: (context, snapshot) {
            // 获取到 Users 对象

            /* AsyncSnapshot 包含异步计算的信息
            它具有以下属性:data —— 异步计算接收的最新数据
            error —— 异步计算接收的最新错误对象
            connectionState —— 美剧connectionState的值,表示与异步计算的连接状态,有四个值
            none,waiting,active,done
             */
            final users = snapshot.data;

            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                // 圆形进度指示器
                return const Center(child: CircularProgressIndicator());
              // 加载成功的情况
              default:
                if(snapshot.hasError) {
                  return const Center(child: Text("Some error occurred!"));
                } else{
                return buildUsers(users!);
                }
            }
          },
        ),
      );
// 创建构造用户的方法
  Widget buildUsers(List<User>users) => ListView.builder(
    physics: const BouncingScrollPhysics(),
    itemCount: users.length,
    itemBuilder: (context, index) {
      final user = users[index];

      return ListTile(
        leading: CircleAvatar(
          backgroundImage: NetworkImage(user.urlAvatar),
        ),
        title: Text(user.username),
        subtitle: Text(user.email),
      );
    },
  );
}

你可能感兴趣的:(flutter,android,java)