三十二. flutter

200 阅读10分钟

1.简介

1.1

  • 2015,Flutter(当时叫Sky) 在Dart开发者峰会上亮相
  • 2018-6,Flutter发布了首个预览版本
  • 2018-12,Flutter1.0发布
  • 2019-9,Flutter1.9发布, 添加Web端支持
  • 2020-9,Flutter1.22发布,带来了对iOS14和Android11的支持

1.2 flutter下载

  • flutter.cn,自行下载安装
PUB_HOSTED_URL  dart的路径
FLUTTER_STORAGE_BASE_URL    https://storage.flutter-io.cn

C:\yrvh\flutter_windows_3.10.5-stable\flutter\packages\flutter_tools\gradle\flutter.gradle
// google()
        maven { url 'https://maven.aliyun.com/repository/google'}
        maven { url 'https://maven.aliyun.com/repository/jcenter'}
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public'}

1.3 AndroidStudio下载

  • https://developer.android.google.cn/studio?hl=zh-cn
  • Android Studio\jbr\bin 设置path环境变量
  • C:\yrvh\flutter_windows_3.10.5-stable\flutter\bin 配置环境变量

1.4 创建项目

  • 命令行创建 flutter create name123
  • 编辑工具创建
  • main.dart是文件入口
  • AndroidStudio安装flutter和dart插件

1.5 目录介绍

文件夹:
.dart_tool
.idea
android
ios
lib
test

文件: 
learn_flutter.iml 记录了某些配置
.gitignore
.metadata 对flutter的版本来做一个记录
.packages 
.pubspec.yaml
.pubspec.lock

2.

2.1 入口函数main

(1) main(List<String> args){}  dart的main函数可以接收一个参数, 但是flutter实际上是没有传过来这样一个参数的. 所以可以省略掉
(2) main() { 
    runApp(Widget);   // 首先需要调用这个函数,  它是flutter框架给我们提供的全局函数
                    // 调用这个函数之前需要导入一个库, flutter/material.dart, runApp(Widget) 里边传入一个widget, flutter里万物皆可Widget
} 
(3) 但是Widget 是个抽象类,不能实例化, 所以只能用它子类的对象. 多态,父类的引用 引用子类
main() {
    runApp(Text("测试"));   // 这样也会报错,因为不知从左网游排版还是从右向左排版
    runAppp(Text("你好", textDirection: TextDirection.ltr);   // 这次可以正常显示
}

2.2 Material设计风格

material是什么呢? (与其相对的是cupertino风格 这个是苹果风格)
    materialGoogle公司推行的一套设计风格, 或者叫设计规范等.
    里面有非常多的设计规范,比如颜色 文字的排版 响应动画与过度 填充等等.
    在Flutter中高度集成了Material风格的Widget;
    在我们的应用中,我们可以直接使用这些Widget来创建我们的应用.
MaterialApp(),有home属性,

main() {
    runApp(
        MaterialApp(
          home: Center(
              child: Text(
                  "你好啊", 
                  //textDirection: TextDirection.ltr 
                  // Material默认就是从左往右,所以这里可以省略
                  // Material默认给Text加了下划线
                  )
          )
        )
    );
}


void main() {
    runApp(
        MaterialApp(
          debugShowCheckedModeBanner: false,   // 去掉右上角debug
          // Scaffold这个东西叫脚手架, 脚手架最主要一个作用帮我们搭建页面
          // Scaffold有两个重要属性appBar和body
          home: Scaffold(
              appBar: AppBar( 
                title: Text("标题")
              ),
              body: Center(
                child: Text(
                      "你好啊", 
                      //textDirection: TextDirection.ltr 
                      // Material默认就是从左往右,所以这里可以省略
                      // Material默认给Text加了下划线
                    )
              )
          )
        )
    );
}

2.3 Widget, StatelessWidget,StatefulWidget

(1)
有状态的Widget: StatefulWidget 在运行过程中有状态需要改变.
无状态的Widget: StatelessWidget 内容是确定的没有状态的改变,没有数据data的改变

StatelessWidget它的数据通常直接写死,或传入一些不可修改的数据.
放在StatelessWidget中的数据必须被定义为final.

(2) build方法
    Flutter在拿到我们自己创建的StatelessWidget时,就会执行它的build方法;
    我们需要在build方法中告诉Flutter,我们的Widget希望渲染什么元素,比如一个Text()
    statelessWidget没办法主动去执行build方法,当我们使用的数据发生变化时,build方法会被重新执行.
    
(3) build方法什么情况下被执行
    当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
    当我们的父Widget(外层)发生改变时,子Widget会被重新构建;
    如果我们Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时.

(4) 优化
void main() => runApp(TYMyApp());

class TYMyApp extends StatelessWidget {
  // Widget 是抽象类,必须实现类里边的抽象方法
  // @override
  // Element createElement() {
  //   return null;
  // }

  // StatelessWidget 类里边也有抽象方法 build
  @override build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        // Scaffold这个东西叫脚手架, 脚手架最主要一个作用帮我们搭建页面
        // Scaffold有两个重要属性appBar和body
        home: Scaffold(
            appBar: AppBar(
                title: Text("标题")
            ),
            body: TYHomeBody(),
        )
    );
  }

}

class TYHomeBody extends StatelessWidget {
  @override
  build(BuildContext context) {
    return  Center(
        child: Text(
          "你好啊",
          //textDirection: TextDirection.ltr
          // Material默认就是从左往右,所以这里可以省略
          // Material默认给Text加了下划线
        )
    );
  }
}


(5) @immutable 这个注解主要的作用是说明当前这个类是不可变的.  StatelessWidget这个类就是一个不可变的类. 一旦这个类不可变,里边定义的所有的成员变量必须是final  

(6) 在Flutter的开发中,所有的Widget都不能定义状态. 因为Widget这个类上边用@immutable来修饰, 所以只要是一个Widget你都是不可变的. 

(7) StatefulWidget也是不能把变化的成员变量定义在外层,  也就是StatefulWidget不能定义状态(状态就是可以变化的成员变量), 创建一个单独的类,这个类负责维护状态. 

class TYContent extends StatefulWidget {
    // 所以在这里我们创建一个,单独的状态类.  实例化StatefulWidget时 createState()会返回一个状态类, 实例化StatelessWidget()时 build会返回一个组件Widget.
   
   @override
   State<StatefulWidget> createState() {  
       return TYContentState();
   }
}

class TYContentState extends State<TYContent> {   // 创建的状态类
    var flag = true;    // State的子类可以定义装态, 也就是可以定义变化的成员变量
    
    // 实现父类的抽象方法
    @override
    Widget build(BuildContext Context) {
        return Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Checkbox(
                value: flag, 
                onChanged: (value){ 
                  setState(() {
                    this.flag = value!;
                  });
                }
              ),
              Text("同意协议")
            ]
          )
        );
    }
}

2.4

12.打包

12.1 安卓打包

(1) 生成秘钥
keytool -genkey -v -keystore ./test.keystore -keyalg RSA -keysize 2048 
            -validity 10000 -alias test123
    
(2)
查看单个证书  keytool -printcert -v -file mydomain.crt
查看证书所有信息   keytool -list -v -keystore test.keystore
用别名查看所有信息 keytool -list -v -keystore test.keystore -alias test123
查看MD5           keytool -list -v -keystore test.keystore  | findstr(或grep) "MD5"
(2) 口令  xxxxwww
(3) 在/android/key.properties
(4) 在/android/app/build.gradle中配置

12.2苹果打包

12.2.1 基础

  • 基本信息和版本信息和Android一致

12.2.2 用户权限配置

  • 在iOS中某些权限,需要用户允许,为了添加这些权限需要配置info.plist文件
  • xcode打开项目下的ios文件夹

12.2.3 流程

(1)申请开发者账号, developer.apple.com
(2)登记套装ID(BundleID)  Identifiers
(3)在AppStoreConnect中创建一个app
(4)新建证书 
    钥匙串, 证书助理, 从证书颁发机构请求证书,
    在官网certificates下发布证书, 
    下载这个证书,
    创建app的profiles文件
    创建完成后下载这个profiles
    在xcode中打包

(5)安装brew
(6)自己安装ruby,  brew install ruby,不用苹果自带的ruby,不要卸载苹果自带的ruby,并改变环境变量
vim ~/.bash_profile
vim ~/.zshrc

(7)安装cocoapods
(8) pod install 


12.2.4 配置相关的证书

  • developer.apple.com/account/
  • 下载和安装证书,电脑才具备发布程序的能力
  • 创建appid, 配置发布者证书(iOS Distribution)

13.Getx

1.简介

  • Getx 是flutter上的轻量且强大的解决方案: 高性能的状态管理,智能的依赖注入和便捷的路由管理.
  • Getx有3个原则:
    • 性能: Getx专注于性能和最小资源消耗.Getx打包后的apk占用大小和运行时的内存占用小.
    • 效率: Getx的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长.
    • 结构: Getx可以将界面,逻辑,依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护.
  • Getx并不臃肿,却很轻量. 如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中. 它众多的功能都在独立的容器中,只有在使用后才会启动.
  • Getx有一个庞大的生态系统,能够在Android,iOS,Web,Mac,Linux,Windows和你的服务器上用同样的代码运行.
  • 通过GetServer可以在你的后端完全重用你在前端写的代码.
  • 此外,通过Get CLI,无论是在服务器还是在前端,整个开发过程都可以完全自动化.
  • 此外,为了进一步提高您的生产效率,我们还为您准备了一些插件.
    • getx_template: 一键生成每个页面必须的文件夹,文件,模板代码等等.
    • Getx Snippets: 输入少量字母,自动提示选择后,可生成常用的模板代码.

2.案例计数器_小试牛刀

(1)步骤一,使用GetMaterialApp替换MaterialApp, GetMaterialApp并不是修改后的MaterialApp,他只是一个预先配置的Widget,他的子组件是默认的MaterialApp. GetMaterialApp会创建路由,注入它们,注入翻译,注入你需要的一切路由导航.
    如果你只用Get来进行状态管理或依赖管理,就没有必要使用GetMaterialApp. GetMaterialApp对于路由,snackbar,国际化,bottomSheet,对话框以及与路由相关的高级apis和没有上下文context 的情况下是必要的.
(2) 步骤二,
    class Controller123 extends GetxController{
        var count = 0.obs;
        increment() => count++;
    }
(3) 步骤三,创建你的界面,使用StatelessWidget节省一些内存, 使用了Get你可能不在需要StatefulWidget
    class Home extends StatelessWidget {
        @override
        Widget build(context) {
            // 使用Get.put实例化你的类, 使其对当下的所有路由可用
            final Controller123 c = Get.put(Controller123());
        }
        
        @override
        Widget build(BuildContext context) {
            return Scaffold(
                appBar: AppBar(
                    title: Text("Getx Demo"),
                    centerTitle: true,
                ),
                body: Container(),
            )
        }
    }

3.snackbar

  • 页面头部提示框

4.dialog

  • 弹出框

5.bottomsheet

  • 底部操作弹窗

6.路由

6.1 普通路由

Get.to(NextPage());   // 导航到该页面
Get.back();   // 要关闭snackbars, dialogs,bottomsheets或任何你通常会用Navigator.pop(context)关闭的东西.

Get.off(NextPage());    // 进入当前页面,并取消 上一个页面的选项(用于闪屏页,登录页面等)
Get.offAll(NextPage());   // 进入当前页面 并 取消之前的所有路由.

Get.arguments;   // 获取上一个页面传过来的参数, 上个页面使用arguments做key

6.2 命名路由

  • 如果你喜欢用别名做路由导航,Get也支持
路由页面的定义: 
void main() {
    runApp(
        GetMaterialApp(
            initialRoute: '/',
            defaultTransition: Transition.zoom,
            getPages: [
                GetPage(name: '/', page: () => MyHomePage()),
                GetPage(name: '/second', page: () => Second()),
                GetPage(name: '/login', page: () => Login(), transition: Transition.zoom)
            ]
        )
    )
}

-----------------
Get.toNamed("/NextScreen");   // 导航到该页面
Get.offNamed("/NextScreen");   // 导航到该页面,并删除前一个页面
Get.offAllNamed("/NextScreen");   // 导航到该页面,并删除前面的所有页面
Get.arguments   // 获取上一个页面传过来的参数, 上个页面使用arguments做key

Get.put(Controller())
Get.find   // 你可以让Get找到一个正在被其他页面使用的Controller,并将它返回来.

  • 动态网页连接, Get提供高级动态URL,就像在Web上一样.
(1)
Get.offAlllNamed("/NextScreen?device=phone&id=55");
Get.parameters["name"]   // 用这种方式来取值
(2)path参数
GetPage(
    name: '/profile/:userid',
    page: () => UserProfile(),
)
Get.toNamed("/profile/7979")
Get.parameters["userid"]   // 也是用这种方式来取值
  • 中间件
在路由跳转之前执行(生命周期函数)
routingCallback: (routing) => {
    print(routing)
}

7.状态管理

7.1 介绍

  • 目前,flutter中有几种状态管理.但是,他们中的大多数都涉及到使用changeNotifier来更新widget, 这对于中大型应用的性能来说是一个很糟糕的方法. 你可以在flutter的官方文档中查看到, ChangeNotifier应该使用1个或最多2个监听器,这使得它们实际上无法用于任何中等或大型应用.

  • Get并不是比任何其他状态管理器更好或更差, Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,既可以单独使用,也可以与其他管理器结合使用.

  • Get有两个不同的状态管理器: 简单的状态管理器(GetBuilder) 和响应式状态管理器Getx

7.2 响应式状态管理器

  • Getx将响应式编程变得非常简单
  • 你不需要创建StreamControllers
  • 你不需要为每个变量创建一个StreamBuilder
  • 你不需要为每个状态创建一个类
  • 你不需要为一个初始值创建一个Get.
(1)
var name = 'tom'.obs   // 只需要在末尾加上.obs 
Obx(()=>Text("${controller.name}"));   // 而在UI中,当你想显示该值并在值变化时更新页面,只需要这样做

(2) 声明一个响应式变量的几种方式
第一种是Rxtype:  
    final name = RxString("tom");
    final name = RxBool(false);
    final name = RxInt(8);
    final name = RxDouble(3.4);
    final name = RxList<Int>([]);
    final name = RxMap<String,int>({});
第二种是Rx<Type> 
    final name = Rx<String>("tom");
    final name = Rx<Bool>(false);
    final name = Rx<Int>(8);
    final name = Rx<Double>(3.4);
    final name = Rx<List<Int>>([]);
    final name = Rx<Map<String,int>>({});
第三种更实用,只需要在末尾加上 .obs
    final name = tom.obs;
    final name = false.obs;
    final name = 8.obs;
    final name = 3.4.obs;
    final name = <Int>[].obs;
    final name = <String, Int>{}.obs;

以上3中取值的时候,要加.value,  name.value

7.3 可以使用obs的地方

(1)
final name = "tom".obs
final age = 22.obs

(2)
class User {
    User({String name, int age});
    var name;
    var age;
}

final user33 = User(name: 'tom', age: 20).obs;
user.update((user)=> {user.name = 'miles', user.age = 20});   // 更新

(3) List和它里面的对象一样,是完全可以观察的.这样一来,如果你在List中添加一个值,它会自动重建使用它的widget.
你也不需要在List中使用.value, 神奇的dart api 允许我们删除它.不幸的是,像Stringint这样的原始类型不能被扩展,使得.value的使用是强制的,  但是如果你使用get和serter来处理这些类型, 这将不是一个问题.

// controller
final String title = "UserInfo".obs
final list = List<User>().obs

// view
Text(controller.title.value)
ListView.builder(
    itemCount: controller.list.length   // List不需要用.value
)

(4)
// model
class User() {
    User({this.name = '', this.age=''});
    String name;
    int age;
}

7.4 Workers

  • Workers可以协助你在事件发生时触发特定的回调.
(1) ever(count1, (aa) => print("$aa 被更改"))
(2) once(count1, (aa) => print("$aa 只在第一次变化时回调"))
(3) debounce(count1, (aa) => print("$aa 每当用户停止操作 1秒后回调"), time: Duration(seconds: 1))   // 防抖
(4) interval(count1, (aa) => print("$aa 忽略一秒内所有变化"), time: Duration(seconds: 1))


8. 控制器 Controller

9. 简单状态管理 GetBuilder

10. 依赖管理

  • Get.put()
  • Get.lazyPut()
  • Get.putAsync()
  • Get.create()

11.Get Cli