Flutter分享

Flutter 简介

Flutter 是 Google 推出并开源的移动应用开发框架,帮助开发者通过一套代码库高效构建多平台应用,支持 iOS、Android、Web、Windows、macOS、Linux、Fuchsia

优点

  1. 跨平台:增加代码复用,降低开发成本
  2. 性能:Flutter 直接将 Dart 代码编译为本地代码运行,再调用 skia 绘图引擎代码,和原生一样,这就少了像 reactnative 和 weex 等先转为原生控件,再系统渲染的步骤
  3. 热重载:修改完代码后 Ctrl+S 就能实时展现在真机界面上,不需要重新安装 apk 包,提高开发效率
    unknown_filename.3
    unknown_filename.4
    unknown_filename.5

缺点

  1. 脱离不开原生,一些功能不支持,需要原生开发
  2. 适配问题,可能会有各种各样的适配问题
  3. 基础库不完善,需要自己写

在现阶段,开始尝试探索和积累沉淀 Flutter 技术能力,逐步的完善,flutter 还是值得尝试一下的,毕竟研发效率就是竞争力。

现状

现在的很多 app 都有进行 flutter 的功能,这是“国内大厂应用在移动端 Flutter 框架使用分析”,地址链接:https://juejin.cn/post/7012382656578977806

常用网址

Flutter 开发文档

Flutter实战

Dart 编程语言概览

pub仓库

Widget

Activity、Fragment、view 在 Flutter 中等价于 Widget.

与 Android view 区别

  • Android 中 View 是可变的,当用户交互或数据更新时,可直接调用 View 的 invalidate 方法重绘,达到更新 UI 的目的。
  • Flutter 的 widget 是不可改变的因此不能直接更新,而必须使用 Widget 的状态。Flutter 的 widget 分为有状态和无状态两种。它们的核心特性是相同的, 每一帧它们都会重新构建,不同之处在于有状态的 Widget 有一个 State 对象,它可以跨帧存储状态数据并恢复它

Stateful widgets

Stateless widgets 是不可变的, 这意味着它们的属性不能改变,所有的值都是最终的.
Stateful widgets(有状态的部件) 持有的状态可能在 widget 生命周期中发生变化. 实现一个 stateful widget 至少需要两个类:

  1. 一个 StatefulWidget 类。
  2. 一个 State 类。 StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在.

stateful widget 将自身的构建委托给 State 对象,State 对象的 build 函数负责构建该 Widget,当用户交互或数据发生变化时,Widget 状态发生改变,调用 State 的 setState 方法通知它,而后 State 根据当前的状态信息,重新构建 Widget tree

  • 在 Android 中,您可以从父级控件调用 addChild 或 removeChild 以动态添加或删除 View。在 Flutter 中,因为 widget 是不可变的,所以没有 addChild。相反,您可以传入一个函数,该函数返回一个 widget 给父项,并通过 布尔值控制该 widget 的创建
  • 在 Flutter 中,一个自定义 widget 通常是通过组合其它 widget 来实现的,而不是继承
  • 某些 widget 属性需要单个 widget(child),而其它一些属性,如 action,需要一组 widgets (children),用方括号[]表示。

生命周期

  • initState () 表示当前 State 将和一个 BuildContext 产生关联,但是此时 BuildContext 没有完全装载完成,如果你需要在该方法中获取 BuildContext ,可以 new Future. delayed (const Duration (seconds: 0, (){//context}); 一下。
  • didChangeDependencies () 在 initState () 之后调用,当 State 对象的依赖关系发生变化时,该方法被调用,初始化时也会调用。
  • deactivate () 当 State 被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会调用。onPause 差不多。(已经消失在新版本中)
  • dispose () Widget 销毁了,在调用这个方法之前,总会先调用 deactivate ()。
  • didUpdateWidge 当 widget 状态发生变化时,会调用。

unknown_filename.3

常用 Widget 和属性

  • Container:父 view,宽高、背景色、圆角、margin

  • Padding:EdgeInsets. fromLTRB

  • Center:居中

  • TextField:输入框(TextEditingController)

  • Expanded:填充剩余布局,组件有个参数 flex, 可以实现比例分配。
    height 如果不设置界面显示会有问题,如果要设置,又不能准确的计算出结果, 可以使用 Expanded

  • BoxDecoration: 圆角,需要放在 Container 里,实现边框、圆角、阴影、形状、渐变、背景图像

  • ShapeDecoration: 实现四个边分别指定颜色和宽度、底部线、矩形边色、圆形边色、体育场(竖向椭圆)、角形(八边角)边色

  • UnderlineTabindicator: 下划线

  • EdgeInsets. only
    symmetric ({vertical, horizontal}):用于设置对称方向的填充,vertical 指 top 和 bottom,horizontal 指 left 和 right。

  • Flutter 中官方提供 CustomScrollView,让我们能够作何 Appbar 折叠的效果,并且很容易就能实现下拉刷新和加载更多。

  • dialog 高度设置不生效将 showBottomsheet 更换成 showAdjustableBottomSheet

  • MediaQuery. removePadding 可以移除组件的边距,有些组件自带有边距

  • expenand 必须和 colum 或 row 一起用,否则 debug 能运行,release 报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RichText(text: TextSpan(
children: [
TextSpan(
text:"*",
style: TextStyle(color: Color.colorAuxRedColor, fontSize: 15)
),
TextSpan (
text: "你好",
style: TextStyle (color: Color. colorText222Color, fontSize: 15)
)
]
)),



Visibility (
visible: true,
//是否保持占位
maintainState: false,
child: Text ("显示"),
),

unknown_filename.6

点击

  • 在 Flutter 中,添加触摸监听器有两种方法: 如果 Widget 支持事件监听,则可以将一个函数传递给它并进行处理。例如,RaisedButton 有一个 onPressed 参数
  • 如果 Widget 不支持事件监听,则可以将该 Widget 包装到 GestureDetector 中,并将处理函数传递给 onTap 参数。
  • InkWell:点击,和 GestureDetector 区别,即使 InkWell 没有子控件,它仍然可以响应点击操作并执行相应的操作或触发回调函数。

MaterialApp

MaterialApp 是我们使用 Flutter 开发中最常用的符合 Material Design 设计理念的入口 Widget。你可以将它类比成为网页中的,且它自带路由、主题色,<\title>等功能。

listView  
CustomScrollView 
ScrollView
SingleChildScrollView

Scaffold

Scaffold 通常被用作 MaterialApp 的子 Widget,它会填充可用空间,占据整个窗口或设备屏幕。Scaffold 提供了大多数应用程序都应该具备的功能,例如顶部的 appBar,底部的 bottomNavigationBar,隐藏的侧边栏 drawer 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Scaffold ({
Key key,
this. appBar, // 标题栏
this. body, // 用于显示当前界面主要内容的 Widget
this. floatingActionButton, // 一个悬浮在 body 上的按钮,默认显示在右下角
this. floatingActionButtonLocation, // 用于设置 floatingActionButton 显示的位置
this. floatingActionButtonAnimator, // floatingActionButton 移动到一个新的位置时的动画
this. persistentFooterButtons, // 多状态按钮
this. drawer, // 左侧的抽屉菜单
this. endDrawer, // 右'侧的抽屉菜单
this. bottomNavigationBar,// 底部导航栏。
this. bottomSheet, // 显示在底部的工具栏
this. backgroundColor,// 内容的背景颜色
this. resizeToAvoidBottomPadding = true, // 控制界面内容 body 是否重新布局来避免底部被覆盖,比如当键盘显示的时候,重新布局避免被键盘盖住内容。
this. primary = true,// Scaffold 是否显示在页面的顶部
})

SizedBox

  • 一般是用来限制孩子控件的大小。
  • 还有这么一种场景也可以使用 SizeBox,就是可以代替 padding 和 container,然后用来设置两个控件之间的间距,比如在行或列中就可以设置两个控件之间的间距主要是可以比使用一个 padding 或者 container 简单方便 (在 Flutter 中可能用不同的控件可以实现到相同的目的,尽量使用越简单的 widget 来实现)
  • 控件在整个手机屏幕中间对齐:ConstrainedBox、SizedBox、Center

布局

Flutter 中通过 Row 和 Column 来实现线性布局,类似于 Android 中的 LinearLayout 控件
row 水平,Column 竖直

  • 对于线性布局,有主轴和纵轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类 MainAxisAlignment 和 CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。
  • mainAxisSize:表示 Row 在主轴 (水平) 方向占用的空间,默认是 MainAxisSize. max,表示尽可能多的占用水平方向的空间,此时无论子 widgets 实际占用多少水平空间,Row 的宽度始终等于水平方向的最大宽度;
  • 而 MainAxisSize. min 表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则 Row 的实际宽度等于所有子组件占用的的水平空间
  • Row 和 Column 都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度
  • 如果 Row 里面嵌套 Row,或者 Column 里面再嵌套 Column,那么只有最外面的 Row 或 Column 会占用尽可能大的空间,里面 Row 或 Column 所占用的空间为实际大小

Stack 类似 FrameLayout 很像,都是可以叠加的现实 View
flutter 中默认组件尺寸单位都是dp
double. infinity,可以使宽度占用尽可能多的空间

当我们使用行(row)的时候,子组件常常因为高度的不同,导致各个子组件里面的内容不能对齐。这个时候我们可以使用 IntrinsicHeight 来保持 row 中各个子组件高度一致,从而便于纵向居中对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum MainAxisAlignment {
//将子控件放在主轴的开始位置
start,
//将子控件放在主轴的结束位置
end,
//将子控件放在主轴的中间位置
center,
//将主轴空白位置进行均分,排列子元素,手尾没有空隙
spaceBetween,
//将主轴空白区域均分,使中间各个子控件间距相等,首尾子控件间距为中间子控件间距的一半
spaceAround,
//将主轴空白区域均分,使各个子控件间距相等
spaceEvenly,
}

Dart 语法

main 函数使用了 (=>) 符号, 这是 Dart 中单行函数或方法的简写。

1
2
// =>是 return 语句的简写
add3 (a, b) => a + b;

变量以下划线(_)开头,在 Dart 语言中使用下划线前缀标识符,会强制其变成私有的。

异步

  • flutter 没有 UI 线程,也没有子线程。也就是说,无论是网络请求,数据处理,页面渲染,都是在同一个线程里面,那怎么保障页面渲染不会 anr 呢?
  • Dart 是一个单线程的语言,遇到有延迟的运算(比如 IO 操作、延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到卡顿,于是通常用异步处理来解决这个问题。当遇到有需要延迟的运算(async)时,将其放入到延迟运算的队列(await)中去,把不需要延迟运算的部分先执行掉,最后再来处理延迟运算的部分。( 延迟队列)

异步async、await和Future的使用技巧

用 async,await,Future 三兄弟来进行处理异步。
async ,它是一个延迟计算的标志,标志了把这个任务放到了延迟运算的队列(await)中,通过 Future 进行返回。

在 Dart 中,有 await 标记的运算,其结果值都是一个 Future 对象,Future 不是 String 类型
Dart 规定有 async 标记的函数,只能由 await 来调用,比如这样:
String data = await getData ();

1
2
3
4
5
//get 请求,请求返回值为 Future<String>类型,即其返回值未来是一个 String 类型的值
getData () async {
//async 关键字声明该函数内部有代码需要延迟执行
return await http.get (Uri.encodeFull (url), headers: {"Accept": "application/json"}); //await 关键字声明运算为延迟执行,然后 return 运算结果
}

async 用于标明函数是一个异步函数,其返回值类型是 Future 类型
await 用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码

await 关键字必须在 async 函数内部使用
调用 async 函数必须使用 await 关键字

then
await 会阻塞流程,等待紧跟着的的 Future 执行完毕之后,再执行下一条语句,而如果用了 Future. then 这个 api,那么就不会等待,直接执行下面的语句,等 Future 执行完了,再调用 then 这个方法。

JSON 转 Bean

自动生成实体类

1
2
3
4
5
6
7
8
9
{
"greeting": "Welcome to quicktype!",
"instructions": [
"Type or paste JSON here",
"Or choose a sample above",
"quicktype will generate code in your",
"chosen language to parse the sample data"
]
}

fromJson map 转对象
toJson 对象转 map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'dart: convert';

Welcome welcomeFromJson (String str) => Welcome.fromJson (json.decode (str));

String welcomeToJson (Welcome data) => json.encode (data.toJson ());

class Welcome {
String greeting;
List<String> instructions;

Welcome ({
required this. greeting,
required this. instructions,
});

factory Welcome.fromJson (Map<String, dynamic> json) => Welcome (
greeting: json["greeting"],
instructions: List<String>. from (json["instructions"]. map ((x) => x)),
);

Map<String, dynamic> toJson () => {
"greeting": greeting,
"instructions": List<dynamic>. from (instructions.map ((x) => x)),
};
}

其他

状态管理框架

[[Flutter 状态管理框架]]

集成成 Android 项目:

android:exported=”true”

  1. flutter create -t module flutter_module
  2. 打开 android 项目 settings. gradle,将
    module 才有这个文件:include_flutter. groovy
1
2
3
4
5
6
rootProject. name = "NativeInFlutter"
include ': app'
include ': flutter_module'
setBinding (new Binding ([gradle: this]))
evaluate (new File (settingsDir. parentFile, 'NativeInFlutter/flutter_module/. android/include_flutter. groovy'
))

这也得改 allprojects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {
google ()
jcenter ()
}
dependencies {
classpath "com. android. tools. build:gradle: 7.0.0"
}
}
allprojects {
repositories {
google ()
jcenter ()
}
}
  1. app 下 build 加:implementation project (‘: flutter’)

项目如何使用

flutter 仓库地址:
代码 clone 到与 Android 工程同级目录

Android 目前有两种依赖方式:
本地依赖(适用于开发调试阶段)
配置文件 repo_projects. xml 中,打开下面这行

远程依赖(适用打包或 flutter 功能稳定阶段)
配置 repo_projects. xml 中,注释下面这行

打包

开发完后,提测或者上线,需要将 flutter 代码打包。目前已经支持了 jenkins 上自动打包 flutter,上传 maven,然后修改配置仓库,增加版本号,接着 push 代码,自动化打包。

jenkins 打包地址:http://10.14.200.4:8880/jenkins_app/view/图书出版/job/android/job/lib/job/app_ta_flutter/build?delay=0sec

红框必选,其他不用写,选择完后点 build
BUSINESS_BRANCH:选自己的开发的 flutter 功能对应的分支
PROPERTIES_BRANCH:要更新到哪个配置仓库的分支
unknown_filename.9

调试

先把 APP 杀死,在启动过程中 attach,可以先点这个

unknown_filename
出现下面这个就可以调试了
unknown_filename.1

使用‘尾随逗号’

Flutter 代码通常涉及构建相当深的树状数据结构,例如在一个 build 方法中。为了获得良好的自动格式化,我们建议您采用可选的尾部逗号。添加尾随逗号很简单:始终在函数、方法和构造函数的参数列表末尾添加尾随逗号,以便保留您的编码格式。这将有助于自动格式化程序为 Flutter 样式代码插入适当的换行符。
unknown_filename.2

两个好用的图片插件

FlutterQuickLocateAsset
FlutterAssetAutoCompletion

使用自定义模板

提高开发效率
unknown_filename.8
unknown_filename.7

Flutter 与原生通信

将 Flutter 集成到现有应用

Flutter 与 Android 的相互通信

可以从 Native 层调用 flutter 层的 dart 代码,也可以在 flutter 层调用 Native 的代码,而作为通讯桥梁就是 MethodChannel,这个类在初始化的时候需要注册一个渠道值。这个值必须是唯一的,并且在使用到的 Native 层和 Flutter 层互相对应。

flutter 调用 Android
注册

1
2
static const nativeChannel =
const MethodChannel ('com. example. flutter/native');

flutter

1
2
Map<String, dynamic> result = {'message': '我从 Flutter 页面回来了'};
nativeChannel.invokeMethod ('goBackWithResult', result);

Android

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MethodChannel nativeChannel = new MethodChannel (flutterEngine.getDartExecutor (), CHANNEL_NATIVE);
nativeChannel.setMethodCallHandler ((methodCall, result) -> {
switch (methodCall. method) {
//回调
case "goBackWithResult":
// 返回上一页,携带数据
ActivityManager.getInstance (). finishActivity (FlutterActivity. class);
Toast.makeText (this, (String) methodCall.argument ("message"), Toast. LENGTH_SHORT). show ();
break;
case "getSendParams":
result.success (getSendParams ());
break;
default:
result.notImplemented ();
break;
}
});

异步

1
2
3
4
5
Future getSendParams () async {
String params = await _SendFeedBackState. nativeChannel
.invokeMethod ('getSendParams', "");
print ("getSendParams:" + params);
}

Android 调用 flutter
Android

1
2
3
4
5
Map<String, Object> result = new HashMap<>();
result.put ("message", message);
// 创建 MethodChannel
MethodChannel flutterChannel = new MethodChannel (flutterEngine.getDartExecutor (), CHANNEL_FLUTTER);
flutterChannel.invokeMethod ("onActivityResult", result);

flutter
setMethodCallHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@override
void initState () {
super.initState ();
Future<dynamic> handler (MethodCall call) async {
switch (call. method) {
case 'onActivityResult':
Fluttertoast.showToast (
msg: call. arguments['message'],
toastLength: Toast. LENGTH_SHORT,
);
break;
case 'goBack':
// 返回上一页
if (Navigator.canPop (context)) {
Navigator.of (context). pop ();
} else {
nativeChannel.invokeMethod ('goBack');
}
break;
}
}

flutterChannel.setMethodCallHandler (handler);
}


Flutter分享
http://peiniwan.github.io/2024/04/79fc12608faf.html
作者
六月的雨
发布于
2024年4月6日
许可协议