前言:Flutter是Google开源的构建用户界面(UI)工具包,帮助开发者通过一套代码库高效构建多平台应用,支持移动、Web、桌面和嵌入式平台。Flutter使用Dart为开发语言,利用Skia绘图引擎,直接通过CPU、GPU进行绘制,不需要依赖任何原生的控件,相比React Native(依赖中间者JSCore引擎)性能更高。
目前Flutter混合栈技术成熟,基础建设完善,百度贴吧、网盘、地图、阅读、输入法等均已接入Flutter,一套代码双端运行,约节省50%人力。话不多说,我们直接开始吧~
一、环境配置:
1.1 下载Flutter SDK
git clone https://github.com/flutter/flutter.git
复制代码
1.2 配置环境变量
编辑~/.bash_profile,将环境变量添加至末尾。(如终端安装了zsh插件,则添加环境变量至 ~/.zshrc)
# FLUTTER_HOME为下载的Flutter文件夹路径
export FLUTTER_HOME=/Users/.../flutter
export PATH=$PATH:$FLUTTER_HOME/bin
export PATH=$PATH:$FLUTTER_HOME/bin/cache/dart-sdk/bin
复制代码
1.3 刷新环境变量
source ~/.bash_profile
source ~/.zshrc(如安装zsh插件)
复制代码
1.4 开发工具
1. Xcode + Android Studio(推荐)
2. Visual Studio Code
以 Xcode + Android Studio为例,配置Android Studio插件:
1.4.1 安装 Flutter,Dart插件
Android Studio - Preferences - Plugins - Marketplace
1.4.2 安装最新 Android SDK Command
Android Studio - Preferences - SystemSettings - Android SDK - SDK Tools - 勾选Android SDK Command-line Tools
1.4.3 运行 flutter doctor
*报错:Unable to find bundled Java version on Flutter
cd /Applications/Android\ Studio.app/Contents/jre
ln -s ../jre jdk
ln -s "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin" jdk
flutter doctor
复制代码
二、工程创建
2.1 创建Flutter项目
flutter create xxx
复制代码
2.2 创建Flutter模块(用于原生集成Flutter)
create --template module xxx
复制代码
2.3 工程结构
bd_flutter
.dart_tool.............记录依赖库信息
.idea..................当前项目配置
android................Android工程目录
iOS....................iOS工程目录
lib....................Flutter代码目录
test...................单元测试目录
web....................Web工程目录
pubspec.yaml...........Pub第三方依赖配置文件,类似Cocoapods、Gradle
复制代码
三、编程范式
在 Flutter 中界面布局使用 Dart 语言声明式编程范式,更易于开发与阅读。
3.1 命令式编程
命令“机器”如何去做事情(注重 how) 。
3.2 声明式编程
告诉“机器”你想要的是什么(注重 what) 。
2009年开始Vue、React、SwiftUI、Flutter以声明式编程为主,正逐步成为大前端的一种编程趋势。
3.3 我们举一个栗子,来帮我我们理解这两者的区别
3.3.1 点击按钮修改文本(OC、Java版本)
3.3.2 点击按钮修改文本(Flutter、SwiftUI版本)
在声明式编程中,首先代码是结构化的;其次,开发者无需关注Label/Text控件更新,引擎会自动根据num值的改变修改引用控件的值。
四、基础架构
Flutter被设计为一个可扩展的分层系统。它可以被看作是各个独立的组件的系列合集,上层组件各自依赖下层组件。组件无法越权访问更底层的内容,并且框架层中的各个部分都是可选且可替代的。从下到上分为三层,依次为:Embedder、Engine、Framework。
4.1 Embedder
Embedder是操作系统适配层,实现了渲染 Surface 设置,线程设置等。
4.2 Engine
Engine层是 Flutter 的核心,它主要使用 C++ 编写,并提供了 Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia 链接:skia.org/)、文本布局、文件及网… IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。
4.3 Framework
Framework 层是一个用 Dart 实现的 UI SDK,包含了动画、图形绘制和手势识别等功能。开发者可以通过 Flutter 框架层与 Flutter 交互,该框架提供了以 Dart 语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:
1、基础的Foundation 类及一些基层之上的构建块服务,如 animation、 painting 和 gestures,它们可以提供上层常用的抽象。
2、渲染层用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。
3、widget层是一种组合的抽象。每一个渲染层中的渲染对象,都在 widgets 层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。
4、Material 和 Cupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 iOS 设计规范。
五、视图渲染
5.1 Widget
Flutter中没有Controller、Activity概念,只有一种控件Widget(相当于View),一切皆 Widget。
Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 Flutter 开发框架中最基本的概念。
两个比较重要的Widget:StatelessWidget和StatefulWidget。
5.2 渲染过程
5.2.1 Flutter引擎不会直接渲染widget树,因为widget是特别不稳定的,会频繁的调用build方法,widget又相互依赖,一旦调用build,后面的widget都会重新创建,直接去解析widget的话会非常消耗性能,布局需要重新计算。由此引出了Element,RenderObject的概念,Flutter引擎解析的是RenderObject树,并非widget。
5.2.2 Widget会转化成RenderObject,但并不是所有的widget都会转成RenderObject。
非RenderObject转化:
//Text -> StatelessWidget -> Widget
class Text extends StatelessWidget {
}
abstract class StatelessWidget extends Widget {
StatelessElement createElement() => StatelessElement(this);
}
abstract class Widget extends DiagnosticableTree {
Element createElement(); // 创建element抽象方法
}
复制代码
RenderObject转化:
//Column -> Flex -> MultiChildRenderObjectWidget - > RenderObjectWidget -> Widget
class Column extends Flex {
}
class Flex extends MultiChildRenderObjectWidget {
// ?法实现
RenderFlex createRenderObject(BuildContext context) {
//返回RenderFlex
return RenderFlex -> RenderBox -> RenderObject
} }
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
}
abstract class RenderObjectWidget extends Widget {
RenderObjectElement createElement();
RenderObject createRenderObject(BuildContext context); // 抽象?法-创建RenderObject
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
}
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
abstract class Widget extends DiagnosticableTree {
Element createElement(); // 抽象?法-创建element
}
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox,
FlexParentData>,
RenderBoxContainerDefaultsMixin<RenderBox,
FlexParentData>,
DebugOverflowIndicatorMixin {
}
abstract class RenderBox extends RenderObject {
}
复制代码
六、混合开发
6.1 Flutter调用原生方法
1.Platform channels
2.Pigeon
3.pub.dev/ 中搜索第三方开源包
以Platform channels为例:Flutter调用原生获取UDID
/*
Flutter代码
*/
static const platform = const MethodChannel("leo.com/getudid");
void getUDID() async {
final result = await platform.invokeMethod("nativeGetUDID"); // 要调?的?法
// final result = await platform.invokeMethod("nativeGetUDID",["flutter参数"]);
setState(() {
_udid = result;
});
}
复制代码
/*
iOS代码
*/
// 1.获取FlutterViewController
let controller: FlutterViewController = window.rootViewController as!
FlutterViewController;
// 2.创建FlutterMethodChannel,跟controller?进制消息通信
let channel = FlutterMethodChannel(name: "leo.com/getudid", binaryMessenger:
controller.binaryMessenger);
// 3.监听channel调??法,当flutter调?nativeGetUDID的时候调?
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping
FlutterResult) in
// 1.判断当前是否是nativeGetUDID
guard call.method == "nativeGetUDID" else {
result(FlutterMethodNotImplemented); // 报?个没有?法的错误
return;
}
call.arguments; //传递过来的参数
// 2.获取UDID
let udid = "xxxx-xxxx-xxxx-xxxx"
result(udid) //回调值
}
复制代码
/*
Android代码
*/
private val CHANNEL = "leo.com/getudid"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
// 1.创建MethodChannel对象
val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
CHANNEL)
// 2.添加调??法的回调
methodChannel.setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
// 2.1.如果调?的?法是nativeGetUDID,那么正常执?
if (call.method == "nativeGetUDID") {
// 2.1.1.调?另外?个?定义?法回去电量信息
val udid = "xxxx-xxxx-xxxx-xxxx";
result.success(udid)
} else {
//?法找不到,回调notImplemented
result.notImplemented()
}
}
}
复制代码
6.2 原?集成Flutter创建Flutter模块
create --template module native_add_flutter
复制代码
6.2.1 iOS集成Flutter
// CocoaPods集成
flutter_application_path = '../native_add_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb’)
复制代码
// 初始化Flutter引擎 , 为引擎起名为leo
let flutterEngine:FlutterEngine = FlutterEngine(name: "leo");
// 启动flutter引擎,默认函数??为main
flutterEngine.run();
let flutterVC = FlutterViewController(engine: engine, nibName: nil, bundle: nil);
flutterVC.modalPresentationStyle = .fullScreen;
self.present(flutterVC, animated: true, completion: nil);
复制代码
6.2.2 Android集成Flutter
// 在gradle进?配置
// 创建Android项?、添加相关的依赖
// 1、修改Android项?settings.gradle
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'native_add_flutter/.android/include_flutter.groovy' // new
))
include ':native_add_flutter'
project(':native_add_flutter').projectDir = new File('../native_add_flutter')
// 2、配置Android项?的build.gradle
dependencies {
...
implementation project(':flutter') //增加flutter依赖
}
// 3、AndroidManifest.xml配置
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDi
rection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
复制代码
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(
FlutterActivity.createDefaultIntent(this)
);
}
}
复制代码
七、案例讲解 - 计数器
flutter create flutterdemo
main.dart
// 导?类
import 'package:flutter/material.dart';
//??函数,程序加载时调?
void main() {
runApp(MyApp()); //调?runApp?法,并初始化MyApp
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) { //初始化会调?build?法
return MaterialApp( //Material为Google的?种UI?格,MaterialApp可为项?配置App标题、主题
等
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'), //设置主?为MyHomePage
);
}
}
//由于点击需要更改Text显示,所以此处继承StatefulWidget
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
//下的按钮的点击事件
void _incrementCounter() {
// setState会标记需要刷新UI
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++; //点击按钮时候,counter+1, 并?动更新UI显示
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar( //配置导航
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center( //配置布局显示在中?
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column( //?种竖向布局?式,相当于listview
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ //返回多个widget数组,
Text(
'You have pushed the button this many times:',
),
Text( '$_counter',//显示_counter的值
style: Theme.of(context).textTheme.headline4,//显示样式,使?主题的headline4
显示
),
],
),
),
floatingActionButton: FloatingActionButton( //?个可点击的按钮,固定在右下?
onPressed: _incrementCounter, //点击事件
tooltip: 'Increment',
child: Icon(Icons.add), //按钮显示为内部?带的add图?
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
复制代码
关于Android的更多学习,大家可以扫描下方二维码领取详细资料
|