vlambda博客
学习文章列表

跨平台开发框架 Flutter 初探

一、背景

蓝海系统,作为运维技术中心的鼎力之作。公司内部越来越多同事离不开该系统,对系统的依赖度越来越大。与此同时,蓝海也迎来了新的挑战。蓝海,目前只能通过浏览器来进行访问。同事在没有携带电脑的情况下,很难登录蓝海系统(通过手机端打开浏览器进行访问,效果也不理想)。因此,越来越多同事反馈蓝海能否出一个移动端的APP呢?我们秉承着运维服务化的理念,积极响应以及服务同事。对蓝海移动端APP化进行立项以及技术性研究。

二、技术选型

在研究移动端技术选型时,发现移动端目前有三种主流的做法

  • Native App(原生开发APP开发模式)
  • Web APP(HTML5 APP 框架开发模型)
  • Hybrid APP(混合模式移动应用)

原生APP 开发的优点

  1. 可轻易调用手机所有硬件实现功能
  2. 速度更快、性能高、整体用户体验最好
  3. 质量安全性很高

原生APP开发的缺点

  1. 开发周期长以及维护成本高(比如 ios 和 android 需 2 端开发 2 个app)
  2. 学习成本高(ios 必须会 swift, android 必须会 Java)

WebAPP 开发的优点

  1. 项目独立,维护更容易,兼容度多平台
  2. 开发周期短,学习成本低
  3. 更新发版更容易

WebAPP 开发的缺点

  1. 体验不好,对设备和网络要求高,经常卡顿,性能差
  2. 无法调用系统硬件

混合APP开发的优点

  1. 维护容易,能调用部分系统硬件,兼容多平台
  2. 开发周期短,学习成本低

混合APP开发的缺点

  1. 可能需要会原生开发以及支持更多的硬件调用
  2. 性能跟原生还是有差距,对设备一定要求
技术 原生开发 H5开发 混合开发
维护 比较简单
性能 较高
学习成本 比较容易

根据不同维度的技术角度来进行对比,发现混合开发模式比较适合。无论从维护成本,性能以及学习成本来说,都是OK的。

混合开发中存在两个主流的框架

  • Flutter
  • React Native

Flutter React Native
Github Start 137K 102K
Fork 21K 21.8K
诞生时间 2017年5月 2015年4月
渲染效率
开发语言 Dart JavaScript

根据上表数据进行对比,Flutter 比 React Native 在 性能和流行度上都比较高。

基于项目技术性和个人发展来说,选择Flutter 无疑是最优的方案

三、基础入门

3.1 Dart 语言

学习 Flutter,得先了解 Dart 语言[1]。通过这几天的学习,Dart语法对于前端同学来说,上手还是很容易的,风格非常相似。

官网 HelloWorld 的例子是

Every app has a main() function. To display text on the console, you can use the top-level print() function:

void main() {
  print('Hello, World!');
}

3.2 环境配置

程序成功运行起来,必须拥有环境。由于 Flutter 是 Google 公司出品的,因此网络方面要求比较苛刻。

具体的安装流程,参考官网 https://docs.flutter.dev/get-started/install

Flutter 支持多种编辑器如:Android Studio、Xcode、Vscode。但是既然作为双端支持跨双端的开发,笔者推荐使用 VSCode。

使用 VSCode 开发 flutter 记得安装两个插件

  1. Flutter

  1. Dart

跨平台开发框架 Flutter 初探


3.3 创建项目

  1. 通过命令来创建项目
flutter create projectname
  1. 通过 VSCode 来创建项目

点击 vscode -> 查看 -> 命令面板

跨平台开发框架 Flutter 初探

  1. 输入 flutter

跨平台开发框架 Flutter 初探


3.4 项目结构

将刚刚创建好的 flutter 项目使用 VSCode 进行打开,目录结构中存在 IOS 和 Android 的环境了。这些我们暂时不用关注。入口文件是 lib/main.dart。

跨平台开发框架 Flutter 初探

打开后,看不懂也没有关系,毕竟 flutter 自带运行案例对新人不太友好。

3.5 启动模拟器

点击 VSCode 右上角的 debug 按照,就可以完成启动(记得先选中 main.dart)

跨平台开发框架 Flutter 初探

一个完整的 flutter 程序就已经启动起来了

跨平台开发框架 Flutter 初探

3.6 Hello World

运行起来发现并不是标准的"Hello World",因此我需要对 main.dart 进行改造

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(
            title: Text("demo1"),
          ),
          body: Text("Hello World!"),
        ));
  }
}

代码中出现的 StatelessWidget 是什么?后面进阶时会提及到

目前代码的层级关系:

MaterialApp -> Scaffold -> body -> Text

Scaffold 是 flutter 的页面脚手架,里面提供了 appBar、body、floatingActionButton、drawer等组件。具体可以查看 Scaffold 源码或者查看 flutter 官网关于 Scaffold 的源码。

跨平台开发框架 Flutter 初探


"Hello World!"终于看到了。这个是很简单的静态页面,一般业务场景是不会出现这个页面的,一般都是有数据进行交互以及变化的。接下来,对这个页面进行改造一下,页面中出现一个按钮,点击之后会切换"Hello World!"文字。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AppPage(),
    );
  }
}

class AppPage extends StatefulWidget {
  @override
  _AppPageState createState() => _AppPageState();
}

class _AppPageState extends State<AppPage{
  late String name;

  @override
  void initState() {
    super.initState();
    name = "Hello Word!";
  }

  void changeName() {
    String a;
    if (name == "Hello Word!") {
      a = "GG";
    } else {
      a = "Hello Word!";
    }
    setState(() {
      name = a;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [Text(name), ElevatedButton.icon(onPressed: changeName, icon: Icon(Icons.add), label: Text("变化"))],
        ),
      ),
      appBar: AppBar(
        title: Text("demo1"),
      ),
    );
  }
}

跨平台开发框架 Flutter 初探

这时接触过 React 的童鞋就不难发现,setState 还是很眼熟的 ^_^。

四、进阶

通过刚刚 HelloWorld 的案例,可能不少的童鞋会发现,类中继承 StatelessWidget 和 StatefulWidget。这两个类分别代表什么意思呢?

还记背景分析的时候,说过 flutter 框架是使用 dart 语言来进行开发的,为什么会选择 dart 语言来进行开发呢?

性能,性能,性能 (重要的事情说三遍!!)

StatelessWidget:无状态变更,UI 静态固化的 Widget,页面渲染性能更高

StatefulWidget:因状态变更可以导致 UI 变更的 Widget,涉及到数据渲染场景,都是用 StatefulWidget(例如上面的例子)

4.1 生命周期

4.1.1 StatelessWidget

StatelessWidget 生命周期只有一个(这就不难解释,为什么 StatelessWidget 页面渲染性能更高了)

  • build

    build 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用。

    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);

      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: AppPage(),
        );
      }
    }

4.1.2 StatefulWidget

StatefulWidget 的生命周期依次是:

  • createState
  • initState
  • didChangeDependencies
  • build
  • didUpdateWidget
  • deactivate
  • dispose

生命周期一:createState

运行 StatefulWidget 组件时,首先会执行构造函数,然后执行 createState 函数。(构造函数并不是生命周期的一部分,具体了解需要去源码查看~)

当 StatefulWidget 组件插入到组件树中时 createState 函数由 Framework 调用,此函数在树中给定的位置为此组件创建 state, 如果在组件树的不同位置都插入此组件,即创建了多个组件。

系统会为每个组件创建单独一个 State ,当组件从组件树中移除,然后重新插入到组件树中时,createState 函数将会被调用创建一个新的 State。

createState 函数执行完毕后表示当前组件已经在组件树中,此时有一个非常重要的属性 mounted 被 Framework 设置为 true。

生命周期二:initState

initState 函数在组件被插入树中时被 FrameWork 调用(在createState 之后),此函数只会被调用一次,子类通常会重写此方法,在其中进行初始化操作,比如刚刚例子中的 name, 加载网络请求中的数据,或者订阅某些通知等业务逻辑。重写该方法时一定要调用 super.initState()

@override
void initState(){
  super.initState();
  // 初始化业务逻辑...
}

生命周期三:didChangeDependencies

当 StatefulWidget 第一次创建的时候, didChangeDependencies 方法会在 initState 方法之后立即调用,之后当 StatefulWidget 刷新的时候,就不会调用了,除非你的StatefulWidget 依赖的InheritedWidget 发生变化之后, didChangeDependencies 才会调用,所以 didChangeDependencies 有可能调用多次。

生命周期四:build

build 的调用方式有两种:1. StatefulWidget 第一次创建的时候,build 方法会在 didChangeDependencies 方法之后立即调用。

2.每当 UI 需要重新渲染的时候,build 都会被调用

所以千万不要在 build 里面做除了创建 Widget 之外的操作,因为这个会影响 UI 的渲染效率。

生命周期五:didUpdateWidget

当组件的 configuration 发生变化时调用此函数,当父组件使用相同的 runtimeType 和 Widget.key 重新构建一个新的组件时,Framework 将更新此 State 对象的组件属性以引用新的组件,然后使用先前的组件作为参数调用此方法。

@override
void didUpdateWidget(covariant StatefulLifecycle oldWidget){
  super.didUpdateWidget(oldWidget);
}

didUpdateWidget 通常用于当前组件与前组件进行对比。

生命周期六:deactivate

当框架从树中移除 State 对象时会调用这个方法。在某些情况下,挪动 State 位置也会触发这个方法

生命周期七:dispose

当框架从树中永久移除此 State 对象时将会调用此方法,与 deactivate 的区别是,deactivate 还可以重新插入到树中,而 dispose 表示 State 对象永远不会在 build。

这里插入一点关于源码

After the framework calls [dispose], the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed.

跨平台开发框架 Flutter 初探

通过对源码的解读,可以发现 mounted 的值决定了 组件是否渲染到 Framework 中。

4.2 路由

还记得之前说的[代码层级](#3.6 Hello World "代码层级")吗?MaterialApp 是作为最外层,因此路由最有可能在这里进行实现。

点击 MaterialApp 源码中查看

跨平台开发框架 Flutter 初探

源码中还提供了如何创建路由的例子

跨平台开发框架 Flutter 初探

根据源码的提示,对之前的 main.dart 进行改造。

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AppPage(),
      routes: {"/home": (context) => HomePage()},
    );
  }
}

新增一个逻辑函数 jumpPage(Navigator 路由跳转类,里面有很多方法。详情要到官网或者源码查看)

void jumpPage(context) {
    Navigator.pushNamed(context, "/home");
  }

将之前按钮点击事件替换成 jumpPage

class _AppPageState extends State<AppPage{
  late String name;

  @override
  void initState() {
    super.initState();
    name = "Hello Word!";
  }

  void changeName() {
    String a;
    if (name == "Hello Word!") {
      a = "GG";
    } else {
      a = "Hello Word!";
    }
    setState(() {
      name = a;
    });
  }

  void jumpPage(context) {
    Navigator.pushNamed(context, "/home");
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScope.of(context).requestFocus(FocusNode());
      },
      child: Scaffold(
        body: Center(
          child: Column(
            children: [
              Text(name),
              TextField(
                decoration: InputDecoration(hintText: "用户名", labelText: "用户名", prefix: Icon(Icons.person)),
              ),
              ElevatedButton.icon(
                  onPressed: () => jumpPage(context), icon: const Icon(Icons.add), label: const Text("变化")),
            ],
          ),
        ),
        appBar: AppBar(
          title: Text("demo1"),
        ),
      ),
    );
  }
}

在lib文件夹下创建 home.dart, 创建 homePage 类

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
          child: Center(
        child: Text("Home"),
      )),
    );
  }
}

这就完成了简单的路由跳转功能啦~

跨平台开发框架 Flutter 初探

4.3 组件

Flutter 提供了很多组件,而且每个组件都是继承 Widget。在我的理解里面:Flutter 万物都是 Widget。

我在演示 demo 时默认导入 material 的包,因此接下来的涉及到的组件都是以 material 为主,少量的原生组件为辅

4.3.1 Text 文本信息

Text(
   String 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,
  }
)
/// 通过源码可知,data 就是我们需要输入的内容。style,可以对文件的样式进行修改
/// 我这边就简单的弄个蓝色的HelloWorld, 字体稍微大一点的
  class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
          child: Center(
        child: Text(
          "Hello World",
          style: TextStyle(color: Colors.blue, fontSize: 40.0),
        ),
      )),
    );
  }
}

效果就长这样了~ 更多的细节需要查看源码或者到官网查看哦

跨平台开发框架 Flutter 初探

4.3.2 Button

material 里面提供了不少关于 Button 类型的 DropdownButton、ElevatedButton 、FloatingActionButton、IconButton、OutlinedButton、PopupMenuButton、TextButton

我这里就以 TextButton 为例子,其他想起请看 https://docs.flutter.dev/development/ui/widgets/material

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
          child: Center(
              child: ClipRRect(
        borderRadius: BorderRadius.circular(4),
        child: Stack(
          children: [
            Positioned.fill(
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Color(0xFF0D47A1),
                      Color(0xFF1976D2),
                      Color(0xFF42A5F5),
                    ],
                  ),
                ),
              ),
            ),
            TextButton(
              onPressed: () {},
              child: const Text("测试"),
              style: TextButton.styleFrom(
                padding: const EdgeInsets.all(16.0),
                primary: Colors.white,
                textStyle: TextStyle(fontSize: 20.0),
              ),
            )
          ],
        ),
      ))),
    );
  }
}

一个炫酷的 TextButton 按钮就完成啦~

跨平台开发框架 Flutter 初探


4.3.3 TextField 输入框

const TextField(
 obscureText: true/// 是否开启密码模式
  decoration: InputDecoration(
   border: OutlineInputBorder(), // 边框
    labelText: 'Password',
  )
)

一个简单又漂亮的密码输入框就完成啦~

跨平台开发框架 Flutter 初探

五、项目演示

上面讲述了那么多关于 flutter 的基础知识,相信大家应该对flutter 有一定的了解以及认知了。

现在就跟随我的步伐开始运行大佬的 demo,看看一款开源的 app 做出来的效果是怎么样的?

跨平台开发框架 Flutter 初探

这个 demo 看起来是不是非常 nice~ 这就是用 flutter 来实现的哦,有兴趣的童鞋记得要进去看看大佬写的代码,以及实现的思路。

六、吐槽flutter

6.1 flutter Dart 语言升级太快

dart 每次升级都会给人带来一定的学习成本,例如:上个版本不需要 const 这个版本突然间就需要强制添加上 const。有些需要 new 有些又不需要 new ,给人带来一定的困扰。

6.2 不能热更新

flutter 在远古版本的时候还是可以支持热更新的,Google 在官网上已经说明无法热更新了。咸鱼团队研发了自己一套热更新方案,但并没有开源出来。对于我们来说,热更新暂时是没有了,只能等待官网提供或者其他大佬开源出来了~。毕竟使用热更新,性能有所下降。有得必有失,Google 的做法基于性能角度来衡量,确实不应该支持热更新。

6.3 反人类嵌套

以前编写 div 的时候,觉得多层嵌套已经很烦了。没想到来了 flutter 嵌套更加恐怖,例如 Padding 都单独拎出来作为一个 Widget。。你就可以想象一下,这个层级是多么恐怖。。

七、结束语

Flutter 现在在大前端上异常火热,越来越多人喜欢使用 Flutter 来开发APP。加上 Flutter 官方已经在安全版本上支持 Web 以及 Windows 开发,这让一份代码变成多端再也不是梦想了。

笔者目前对 Flutter 的认识也是处于比较浅的地步,研究不深入,如有疏漏,还请指出~





END




1% 



100


跨平台开发框架 Flutter 初探



跨平台开发框架 Flutter 初探 

跨平台开发框架 Flutter 初探 跨平台开发框架 Flutter 初探
  跨平台开发框架 Flutter 初探
跨平台开发框架 Flutter 初探
 
y w j t s h a r e