lzj7800623 发表于 2022-8-2 11:34

flutter介绍





# flutter介绍

## flutter是什么

>Flutter 是Google开源的**UI工具包**,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。Flutter 开源、免费,拥有宽松的开源协议,适合商业项目。

对于应用层开发人员来说,flutter就是一个用于绘制UI界面的SDK,一套代码,多端运行。

## 跨平台方案对比

自移动开发兴起以来,跨平台技术就不断涌现,从webview到RN,再到现在的flutter,可以说是一个逐步完善的过程,下面是跨平台技术的对比:

### Web/Hybrid:

也被成为Hybrid技术,它是基于web h5界面来实现跨平台的,市面上有些应用为了减少开发成本,APP就是个壳子,所有界面都在h5中,系统服务则通过一个中间层桥接到JaveScript中去,APP只需要一个**webview**即可。

hybrid技术本身是没什么问题的,拉新、活动等等都可以用到H5页面。但是APP完全是个壳,所有界面都在h5中,把APP当浏览器用,那么用户体验必定是极差的,原生性能也得不到发挥。所以必须明确一点:移动应用不是浏览器。

![](http://img.tupobi.top/webview%E8%B7%A8%E5%B9%B3%E5%8F%B0.webp)

### JSCore(ReacNative, Weex):

记得RN兴起的时候好多大厂也在用,毕竟是脸书Facebook开发维护的,国内开发人员也争相学习。

这种方式比起web h5来说,不仅将系统服务桥接到JavaScript中,并且将原生UI也桥接过去了。也就是说,一套JS代码通过DOM树可以映射为原生不同平台的UI,确实比上面那种好多了。

但是缺点也很明显:

1. 在渲染UI前先要执行映射操作,在滑动时想保证60FPS很难,用户可以感觉到很明显的掉帧。
2. 就算映射为原生控件了,那么不同平台上展现出的UI还是有平台差异,没有真正达到跨平台的目的。

![](http://img.tupobi.top/RN%E8%B7%A8%E5%B9%B3%E5%8F%B0.webp)

### 原生

苹果2008年发布iOS,Google 2009年发布Android,它们的SDK是基于两种不同的编程语言Objective-C 和 Jave.现在又有了Swift和Kotlin。

![](http://img.tupobi.top/%E5%8E%9F%E7%94%9F%E7%AE%80%E5%8D%95%E6%9E%B6%E6%9E%84.webp)

上面是原生App的一个简单架构,开发人员直接调用平台SDK进行UI开发。由于语言及SDK的不同,所以开发人员必须为两个平台分别开发App。

### Flutter

![](http://img.tupobi.top/flutter%E8%B7%A8%E5%B9%B3%E5%8F%B0.webp)

- 本地服务:Flutter使用Dart语言开发,Dart可以被编译(AOT)成不同平台的**本地(native)代码**,让Flutter可以直接和平台通讯而不需要一个中间的桥接过程,从而提高了性能。

- UI渲染:

![](http://img.tupobi.top/flutter%E4%B8%8E%E5%8E%9F%E7%94%9F%E7%BB%98%E5%88%B6%E5%8E%9F%E7%90%86.jpg)

![](http://img.tupobi.top/flutter%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B2.webp)

可以看到,在绘制方面,android原生使用jni(java native interface)调用C的Skia引擎,而flutter是RenderObject调用skia引擎进行绘制,所以在底层绘制方面,flutter和原生其实没什么区别。

widget tree和element tree是一对一的关系,但是由于缓存原因,最后真正渲染的render object会减少。

## flutter优缺点

### 优点

- UI性能:在**保证原生级性能**的前提下实现了**UI完全跨平台**。
- 开发方面:
1. **热重载**,ctrl + s,随时更新UI。
2. 上手非常简单,和微信小程序类似,写UI就像在写配置一样。而且dart在一定程度上兼容了java的语法,几乎没有太大差别,android开发非常容易上手。
3. 能有效降低人力成本
- 路由设计和动画设计优秀,可以通过await关键字直接获取出栈页面的返回值,android需要startActivityForResult。

### 缺点

- 依赖原生:系统服务依赖原生,如语音、摄像头等硬件方面依赖原生,期待后面第三方库。
- 生产包较大:由于引入了flutterlib.so库以及自带的material/cupertino asset资源,比原生大8M左右。
- UI编译阶段不可视化:可读性较差,不像android有xml可以直接看到视图。这样widget多的话找一个widget可能得找半天。
- widget API提供较少,特别是ListView滑动组件。开源库较少,不比原生丰富。

## dart特点

### 异步非常简单

>Dart 是单线程模型,因为它天然不存在资源竞争和状态同步的问题。这就意味着,一旦某个函数开始执行,就将执行到这个函数结束,而不会被其他 Dart 代码打断。所以 Dart 中并没有线程,只有 Isolate(隔离区)。Isolate之间不会共享内存,就像几个运行在不同进程中的 worker,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信

```dart
void main() {
printName();
}

Future<void> printName() async {
String name = await getNameFromNet();
print(name);
}

Future<String> getNameFromNet() async {
await Future.delayed(Duration(milliseconds: 1000));
return "name";
}
```

这里模拟联网请求操作,在一秒后返回name,打印操作同步等待联网请求完毕才执行。

getNameFromNet返回了一个Future对象,表示返回值现在不能给到,在未来某个时间点给到再return。

### 可选参数

```dart
const Text(
    this.data,
    {
      Key key,
      this.style,
      this.strutStyle,
      this.textAlign,
      this.textDirection,
      this.locale,
      this.softWrap,
      this.overflow,
      this.textScaleFactor,
      this.maxLines,
      this.semanticsLabel,
      this.textWidthBasis,
      this.textHeightBehavior,
    }
) : assert(
```

这是flutter Text widget的构造方法,可以看到,只有text的data是必传参数,其他参数都是可选的用{}括起来。这也是为什么flutter就像在写配置的原因,主要是依赖dart特性,方法可选参数。

### 兼容java语法且比java简单

```dart
// 定义类
class Person {
// 基本数据类型
String name;
int age;
double height;

// 构造方法
Person(this.name, {this.age, this.height});
}

void main() {
// 创建对象,new可以省略
Person person = Person("lzj", age: 23, height: 175.0);
// 方法调用
introduce(person);
}

void introduce(Person person) {
print("My name is ${person.name}, Im ${person.age} years old and ${person.height} cm");
}
```

>My name is lzj, Im 23 years old and 175.0 cm.

## flutterforweb

配置好flutter开发环境后,在项目目录下运行指令下载web开发支持:

```shell
flutter packages pub global activate webdev
```

运行web服务指令:

```bash
flutter packages pub global run webdev serve
```

![](http://img.tupobi.top/flutter_web_demo1.png)

## flutter开发

### hot reload热重载

只需要ctrl s保存即可热重载,部分服务端下发数据无法热重载的话ctrl shift \,瞬间重启程序也可以热重载。对于开发来说是非常方便的。

### 声明式+指令式

相对于原生的指令式更新UI,flutter提供了声明式+指令式模式,比如:

在android原生中,我们在xml中声明UI,在java中指令式修改UI,比如给一个TextView赋值:

```java
textView.setText("...");
```

setText就好比一个指令,告诉CPU要去重新渲染TextView的值了,这个是需要开发人员手动调用的。

而声明式则不需要手动调用来改变UI状态,所有widget都是一开始声明好的,如果widget状态发生改变才去build渲染,而widget状态又是由数据驱动的,所以:数据 ->widget状态 -> 渲染。

```dart
Text(data);
```

Text声明好之后就就不用管了,如果要改变Text值,只需要改变data即可,而不直接对Widget进行操作。

但flutter也是支持指令式的,比如:

```dart
widget.addWidget();
```

### 三种运行模式

- debug:支持热重载、支持性能检测,开发调试用此模式。
- profile:不支持热重载,支持性能检测,一般性能检测时用此方式运行,既不会因为支持热重载降低性能,同时也可以检测程序性能。
- release:不支持热重载和性能检测,用于生产,性能最高。

### 项目结构

![](http://img.tupobi.top/flutter%E9%A1%B9%E7%9B%AE%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%8411.png)

### 一切都是widget

在dart中,一切都是object,不同于java,一个int也是对象,默认初始值是null,而java的int是基本数据类型,不是对象,初始值是0。

在flutter中,一切都是widget,一个文本是widget,一个输入框是widget,一张图片也是由widget负责加载(网络、本地),所以一切UI都是widget。他们的父类要么是StatelessWidget,要么是StatefullWidget。

### 有状态和无状态widget

widget分为有状态和无状态,无状态的widget不需要通过状态来改变自己,而有状态的widget需要通过状态改变自己,势力代码如下:

```dart
class StateWidgetPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text("widgets' state"),
      ),
      body: Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
          TextWithoutState(),
          SizedBox(
            height: 50,
          ),
          TextWithState(
            interval: 2,
          ),
      ],
      ),
    );
}
}

class TextWithoutState extends StatelessWidget {
@override
Widget build(BuildContext context) {
    return Text("我是一个无状态的文本widget");
}
}

/// 一个无状态的text widget
class TextWithState extends StatefulWidget {
final int interval;

const TextWithState({Key key, this.interval}) : super(key: key);

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

/// 一个有状态的text widget
class _TextWithStateState extends State<TextWithState> {
String _text = "";
Timer _timer;

@override
void initState() {
    _timer = Timer.periodic(Duration(seconds: widget.interval), (timer) {
      setState(() {
      _text = "我是一个有状态的widget,每${widget.interval}秒更新自己的状态:${_getCurrentTime()}";
      });
    });
    super.initState();
}

@override
void dispose() {
    _timer.cancel();
    _timer = null;
    super.dispose();
}

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

String _getCurrentTime() {
    return DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch).toString().substring(11, 19);
}
}
```

![](http://img.tupobi.top/widget%E7%9A%84%E7%8A%B6%E6%80%81.jpg)

### 使用Scaffold快速定制页面

Scaffold翻译为中文是脚手架的意思,我们可以利用Scaffold快速实现一个页面,头部标题栏,中间body页面内容,底部(导航栏、悬浮按钮、底部工具栏等等),都在Scaffold中,我们只需要对其进行配置即可:

```dart
class ScaffoldPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text("头部标题栏"),
      actions: <Widget>[
          Icon(Icons.delete),
          SizedBox(width: 20),
          Icon(Icons.cancel),
          SizedBox(width: 20),
          Icon(Icons.more),
          SizedBox(width: 20),
      ],
      ),
      body: Center(
      child: Text("页面部分"),
      ),
      bottomSheet: Container(
      height: 50,
      width: double.infinity,
      color: Colors.red,
      child: Center(child: Text("底部工具栏")),
      ),
      floatingActionButton: Container(
      margin: EdgeInsets.only(bottom: 50),
      child: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: (){
            print('点击悬浮按钮');
          },
      ),
      ),
    );
}
}
```

![](http://img.tupobi.top/use_scaffold.jpg)

### CustomListView强大的列表支持

原生需要多个ViewHolder支持,ViewHolder过多会增加文件数量,flutter只需要一个CustomListView即可往里面放不同类型的widget。实例代码:

```dart
class CustomListViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
    return Scaffold(
      body: _getListWidget(),
    );
}

Widget _getListWidget() {
    return CustomScrollView(
      slivers: <Widget>[
      //AppBar,包含一个导航栏
      SliverAppBar(
          pinned: true,
          expandedHeight: 250.0,
          flexibleSpace: FlexibleSpaceBar(
            title: const Text('Custom List View'),
            background: Image.asset(
            "assets/images/cover_img.jpg",
            fit: BoxFit.cover,
            ),
          ),
      ),

      SliverPadding(
          padding: const EdgeInsets.all(8.0),
          sliver: new SliverGrid(
            //Grid
            gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2, //Grid按两列显示
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
            ),
            delegate: new SliverChildBuilderDelegate(
            (BuildContext context, int index) {
                //创建子widget
                return new Container(
                  alignment: Alignment.center,
                  color: Colors.cyan,
                  child: new Text('grid item $index'),
                );
            },
            childCount: 20,
            ),
          ),
      ),
      //List
      new SliverFixedExtentList(
          itemExtent: 50.0,
          delegate: new SliverChildBuilderDelegate((BuildContext context, int index) {
            //创建列表项
            return new Container(
            alignment: Alignment.center,
            color: Colors.lightBlue,
            child: new Text('list item $index'),
            );
          }, childCount: 50 //50个列表项
            ),
      ),
      ],
    );
}
}
```

![](http://img.tupobi.top/custom_list_view.jpg)

## flutterVS原生android

>文档:flutterVSnative.md
>链接:http://note.youdao.com/noteshare?id=7122d6e0e841545dbbb3d54212b68572&sub=46E7B0FBC27549319467136DEDFC7A67

## 与原生交互

### android

```dart
class NativeChannelTestPage extends StatefulWidget {
@override
_NativeChannelTestPageState createState() => _NativeChannelTestPageState();
}

class _NativeChannelTestPageState extends State<NativeChannelTestPage> {
MethodChannel _methodChannel;

@override
void initState() {
    _methodChannel = const MethodChannel("top.tupobi.demo/channel_connection");
    _methodChannel.setMethodCallHandler((handler) {
      switch (handler.method) {
      case "flutterFunction":
          print("native invoke dart function message = ${handler.arguments["message"]}");
          break;
      }
      return null;
    });

    super.initState();
}

@override
Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text("Native Channel"),
      ),
      body: Column(
      children: <Widget>[
          FlatButton(
            child: Text("调用native吐司"),
            onPressed: () {
            _methodChannel.invokeMethod("nativeToast", {"message": "flutter"});
            },
          )
      ],
      ),
    );
}
}
```

```kotlin
class NativePluginForFlutter : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {

    private lateinit var mRootActivity: Activity
    private lateinit var mMethodChannel: MethodChannel

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
      val methodChannelName = "top.tupobi.demo/channel_connection"
      mMethodChannel = MethodChannel(binding.binaryMessenger, methodChannelName)
      mMethodChannel.setMethodCallHandler(this)
      println("native 初始化mMethodChannel")
      mMethodChannel.invokeMethod("flutterToast", mapOf("message" to "messageFromNative"))
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
      when (call.method) {
            "nativeToast" -> {
                val message = call.argument<String>("message")
                Toast.makeText(mRootActivity, "toast from native, message = $message", Toast.LENGTH_LONG).show()
                mMethodChannel.invokeMethod("flutterFunction", mapOf("message" to "messageFromNative"))
            }
      }
    }

    override fun onDetachedFromActivity() {
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
      this.mRootActivity = binding.activity
    }

    override fun onDetachedFromActivityForConfigChanges() {
    }
}
```

## 状态管理

简单的项目可以直接setState()进行状态管理即可,简单的状态管理如下图:

![](http://img.tupobi.top/flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%861.webp)

但是当项目体积庞大起来的时候,随着业务逻辑复杂度增加,再使用setState()进行状态管理就会出现下面这种情况,widget之间的状态异常复杂,且业务逻辑与UI耦合在一起,这样非常不利于代码的维护和新功能的扩展。

![](http://img.tupobi.top/flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%862.webp)

### 使用provider

provider的原理是使用InheritedWidget实现共享数据,父类定义的数据可以被所有子widget共享,这样数据一旦发生变化,只需要发送一个通知,所有监听该数据变化的widget感知到后就可以改变自身的状态。

共享数据:

```dart
class SharedData with ChangeNotifier {
UserModel _userModel;
GoodModel _goodModel;

void updateUserModel() {
    UserModel userModel = UserModel();
    userModel.name = Random().nextInt(1000).toString();
    userModel.age = Random().nextInt(100);
    _userModel = userModel;
    notifyListeners();
}

void updateGoodModel() {
    GoodModel goodModel = GoodModel();
    goodModel.goodName = Random().nextInt(1000).toString();
    goodModel.goodPrice = Random().nextInt(100);
    _goodModel = goodModel;
    notifyListeners();
}

get userModel => _userModel;

get goodModel => _goodModel;

@override
String toString() {
    return 'SharedData{userModel: $userModel, goodModel: $goodModel}';
}
}

class GoodModel {
String goodName;
int goodPrice;

@override
String toString() {
    return 'GoodModel{goodName: $goodName, goodPrice: $goodPrice}';
}
}
```

监听者(widget):

```dart
class TextWidget1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
    print("TextWidget1 build");
    // 这种方式context相关全部重建
//    final userModel = Provider.of<SharedData>(context).userModel;

    // 这种方式与consumer一致
//    final userModel = context.watch<SharedData>().userModel;

    // 这种方式修改SharedData中其他属性不会重建,与selector一致
    final userModel = context.select((SharedData sharedData) => sharedData.userModel);
    return Container(
      width: 80.w,
      child: Text(
      userModel == null ? "null" : userModel.toString(),
      ),
    );
}
}
```

数据变化事件:

```dart
class ProviderTestPage2 extends StatelessWidget {
final SharedData _sharedData = SharedData();

@override
Widget build(BuildContext context) {
    print("ProviderTestPage2 build");
    return Scaffold(
      appBar: AppBar(
      title: Text("provider test2"),
      ),
      body: MultiProvider(
      providers: [
          ChangeNotifierProvider(create: (_) => _sharedData),
      ],
      child: Container(
          child: Column(
            children: <Widget>[
            FlatButton(
                onPressed: () {
                  _sharedData.updateUserModel();
                },
                child: Text("change user model"),
            ),
            FlatButton(
                onPressed: () {
                  _sharedData.updateGoodModel();
                },
                child: Text("change good model"),
            ),
            Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  TextWidget1()
                ],
            )
            ],
          ),
      ),
      ),
    );
}
}
```

可以看到,观察者对要观察的数据的粒度是可以把控的:

- 最粗粒度:Provider.of<SharedData>这种是最粗的粒度,一旦ShareData包括其中内容发生改变就会通知所有context相关的widget进行build,这种方式是不可取的。
- 中等粒度:context.watch<SharedData>,这种对context进行了筛选,只有当ShareData及其中内容发生改变时,只会将事件通知给监听者,没有监听的widget是不会build的。
- 细粒度:context.select((SharedData sharedData) => sharedData.userModel),通过高阶函数筛选ShareData中的成员变量进行监听,如果没被监听的成员变量改变是不会通知该widget的。

### 其他状态管理方案

还有其他状态管理方案,比如bloc还有咸鱼推出的fishbloc等,感兴趣可以自行了解下。

## flutter未来可期

未来flutter在谷歌的计划中可能不仅仅是一种跨平台技术,谷歌开发的新一代操作系统**Fuchsia**也是使用flutter进行开发。假如华为**鸿蒙**系统能与现如今的安卓操作系统平分秋色,那未来肯定还会和Fuchsia一战。

lzj7800623 发表于 2022-8-2 14:38

ttt12 发表于 2022-8-2 13:32
flutter还是很不错的吧

哈哈哈 是的

我今天是大佬 发表于 2022-8-2 14:48

我还是只喜欢用原生的java

virsnow 发表于 2022-9-29 14:45

牛啊!爱了爱了!

virsnow 发表于 2022-9-30 18:43

感谢分享宝贵经验
页: [1]
查看完整版本: flutter介绍