Flutter 技术分享

技术演进

伴随着 Android、iOS 等智能手机的不断普及,移动端已逐步取代 PC 端,成为兵家必争之地。正所谓“得移动端者得天下”,移动端已成为互联网领域最大的流量分发入口,一大批互联网公司正是在这大趋势下崛起。

为什么需要跨平台技术呢

伴随着移动互联网的高速发展,公司间竞争越来越激烈,如何将好想法快速落地、快速试错,成为备受关注的问题。提升研发效率、缩短研发周期,降低成本,让新产品新功能以最快的速度同时抵达 Android、iOS 等多端用户,成为了许多公司移动端技术的选型基础。
为了解决这一痛点。跨平台技术便应运而生,各大互联网公司为此都投入大量人力,于是出现了各种跨平台技术框架,面对移动领域的跨平台技术方案的层出不穷,又该如何做技术选型呢。

跨平台的技术划分

跨平台技术大致可分为三大类 :
1.依赖于Webview,性能体验差 如Cordova,小程序
2.使用JavaScript作为编程语言,通过中间层转化为原生控件来渲染UI界面,比如React Native、Weex。
3.自行实现一套渲染框架,可通过调用skia等方式完成自渲染,而不依赖于原生控件,如Flutter、Unity

技术选型

我认为的移动端的跨端技术方案,关注的分为4个方面:
1.研发效率:
代码复用,减少多端差异的适配工作量,降低开发成本,专注业务开发 。这也是跨平台技术会出现的重要原因之一
2.多端一致性,
很多产品在多端UI设计上,往往是整体风格统一,所以业务方采用原生各自独立开发完成后,还需额外花不少时间来修改UI以保证多端一致性。
3.性能体验
一般跨端技术方案拥有以上两种优势,但在性能方面比原生流畅更差些。如果哪天跨平台技术能和Native一样流畅,那么Native 可能就要退出历史舞台了。

Flutter技术优势

Flutter是彻底的跨平台方案,既没有采用webView,也没有采用JS桥接原生控件,而是自行实现一套UI框架,在引擎底层通过Skia渲染到屏幕。对于UI之外所需要使用的移动设备自身提供的服务,比如相机、定位、屏幕触摸等,则采用Platform Channels跟原生系统通信的方式来实现

1.高效率
采用dart语言编写代码,一套代码适用多个平台(Android、iOS、Web),以及高效的Hot Reload能快速辅助调试。

2.高一致性:实现UI像素级的控制,Flutter渲染引擎依靠跨平台Skia图形库来实现,仅依赖系统图形绘制相关的接口

3.高性能
image.png

通过一张渲染原理来看Flutter的性能优势,作为谷歌亲儿子,Flutter框架调用Dart Framework层,再直接调用到skia来渲染界面,并没有经过原生Framework过程,可见其渲染性能并不会弱于Native技术,这是一个性能上限很高的跨平台技术。

当然,不得不说目前的Flutter确实不够尽善尽美,会存在一些不够尽善尽美之处,比如生态不够健全,包体积问题,还有大天朝的网络问题,但其该方案的上限比较高,想象空间比较大,相信更多开发者参与进来,经过更多打磨,未来会做得更好

Flutter未来趋势

目前Flutter主要在Android/iOS/Web/Windows跨端,后续可能会嵌入式设备。Fuchsia是Google内部正在开发的一款新的操作系统,采用Flutter作为系统默认的UI框架,也就是说Flutter天然支持Fuchsia,这无疑让Flutter在众多的跨平台方案更有优势.

小🌰:
滑动列表的实现

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as W;
import 'package:flutter/rendering.dart';

class SliverListDemoPage extends StatefulWidget {

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

class _SliverListDemoPageState extends State<SliverListDemoPage>
    with SingleTickerProviderStateMixin {
  int listCount = 30;

  List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
      ///头部信息
      SliverPersistentHeader(
        delegate: SliverHeaderDelegate(
          maxHeight: 180,
          minHeight: 180,
          vSync: this,
          snapConfig: FloatingHeaderSnapConfiguration(
            curve: Curves.bounceInOut,
            duration: const Duration(milliseconds: 10),
          ),
          child: Container(
            color: Colors.redAccent,
          ),
        ),
      ),
      SliverOverlapAbsorber(
        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
        sliver: SliverPersistentHeader(
          pinned: true,

          /// SliverPersistentHeaderDelegate 的实现
          delegate: SliverHeaderDelegate(
              maxHeight: 60,
              minHeight: 60,
              changeSize: true,
              vSync: this,
              snapConfig: FloatingHeaderSnapConfiguration(
                curve: Curves.bounceInOut,
                duration: const Duration(milliseconds: 10),
              ),
              builder: (BuildContext context, double shrinkOffset,
                  bool overlapsContent) {
                ///根据数值计算偏差
                var lr = 10 - shrinkOffset / 60 * 10;
                return SizedBox.expand(
                  child: Padding(
                    padding: EdgeInsets.only(
                        bottom: 10, left: lr, right: lr, top: lr),
                    child: Row(
                      mainAxisSize: MainAxisSize.max,
                      children: <Widget>[
                        Expanded(
                          child: Container(
                            alignment: Alignment.center,
                            color: Colors.orangeAccent,
                            child: TextButton(
                              onPressed: () {
                                setState(() {
                                  listCount = 30;
                                });
                              },
                              child: const Text("按键1"),
                            ),
                          ),
                        ),
                        Expanded(
                          child: Container(
                            alignment: Alignment.center,
                            color: Colors.orangeAccent,
                            child: TextButton(
                              onPressed: () {
                                setState(() {
                                  listCount = 4;
                                });
                              },
                              child: const Text("按键2"),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              }),
        ),
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("SliverListSticky"),
      ),
      body: Container(
        child: NestedScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          headerSliverBuilder: _sliverBuilder,
          body: CustomScrollView(
            slivers: [
              W.Builder(
                builder: (context) {
                  return SliverOverlapInjector(
                      handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                          context));
                },
              ),
              SliverList(
                delegate: SliverChildBuilderDelegate(
                      (context, index) {
                    return Card(
                      child: Container(
                        height: 60,
                        padding: const EdgeInsets.only(left: 10),
                        alignment: Alignment.centerLeft,
                        child: Text("Item $index"),
                      ),
                    );
                  },
                  childCount: 100,
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

///动态头部处理
class SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
  SliverHeaderDelegate(
      {required this.minHeight,
        required this.maxHeight,
        required this.snapConfig,
        required this.vSync,
        this.child,
        this.builder,
        this.changeSize = false});

  final double minHeight;
  final double maxHeight;
  final Widget? child;
  final Builder? builder;
  final bool changeSize;
  final TickerProvider vSync;
  final FloatingHeaderSnapConfiguration snapConfig;
  AnimationController? animationController;

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => math.max(maxHeight, minHeight);

  @override
  TickerProvider get vsync => vSync;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    if (builder != null) {
      return builder!(context, shrinkOffset, overlapsContent);
    }
    return child!;
  }

  @override
  bool shouldRebuild(SliverHeaderDelegate oldDelegate) {
    return true;
  }

  @override
  FloatingHeaderSnapConfiguration get snapConfiguration => snapConfig;
}

typedef Widget Builder(
    BuildContext context, double shrinkOffset, bool overlapsContent);

在 Flutter 里我们常见的滑动列表场景,大致是由三部分组成:
Viewport : 它提供的是一个“视窗”的作用,也就是列表所在的可视区域大小;
Scrollable :它主要通过对手势的处理来实现滑动效果 ,比如VerticalDragGestureRecognizer 和 HorizontalDragGestureRecognizerRenderSliver, : 它主要是用于在 Viewport 里面布局和渲染内容

一般情况下 Viewport 和 Scrollable 的实现都是很通用的,所以一般在 Flutter 里要实现不同的滑动列表,就是通过自定义和组合不同的 Sliver 来完成布局
ListView 使用的是 SliverFixedExtentList 或者SliverList
GridView 使用的是 SliverGrid
PageView 使用的是 SliverFillViewport
ListView 是由 Viewport+ Scrollable 和一个RenderSliver 组成,所以在ListView 里只会有一个 RenderSliver而不是多个,想使用多个 RenderSliver 需要使用 CustomScrollView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容