吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2617|回复: 5
收起左侧

[其他转载] flutter介绍

[复制链接]
lzj7800623 发表于 2022-8-2 11:34

[TOC]

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当浏览器用,那么用户体验必定是极差的,原生性能也得不到发挥。所以必须明确一点:移动应用不是浏览器。

JSCore(ReacNative, Weex):

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

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

但是缺点也很明显:

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

原生

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

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

Flutter

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

  • UI渲染:

    可以看到,在绘制方面,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)上传递消息通信

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。

可选参数

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简单

// 定义类
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.

flutter  for  web

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

flutter packages pub global activate webdev

运行web服务指令:

flutter packages pub global run webdev serve

flutter开发

hot reload热重载

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

声明式+指令式

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

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

textView.setText("...");

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

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

Text(data);

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

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

widget.addWidget();

三种运行模式

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

项目结构

一切都是widget

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

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

有状态和无状态widget

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

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);
  }
}

使用Scaffold快速定制页面

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

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('点击悬浮按钮');
          },
        ),
      ),
    );
  }
}

CustomListView强大的列表支持

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

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[100 * (index % 9)],
                  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[100 * (index % 9)],
              child: new Text('list item $index'),
            );
          }, childCount: 50 //50个列表项
              ),
        ),
      ],
    );
  }
}

flutterVS原生android

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

与原生交互

android

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"});
            },
          )
        ],
      ),
    );
  }
}
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()进行状态管理即可,简单的状态管理如下图:

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

使用provider

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

共享数据:

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):

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(),
      ),
    );
  }
}

数据变化事件:

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一战。

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
brucezhuang + 1 + 1 谢谢@Thanks!
BeyondTheDawn + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| lzj7800623 发表于 2022-8-2 14:38
ttt12 发表于 2022-8-2 13:32
flutter还是很不错的吧

哈哈哈 是的
我今天是大佬 发表于 2022-8-2 14:48
virsnow 发表于 2022-9-29 14:45
virsnow 发表于 2022-9-30 18:43
感谢分享宝贵经验
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 02:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表