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 和 HorizontalDragGestureRecognizer
;RenderSliver
, : 它主要是用于在 Viewport 里面布局和渲染内容
一般情况下 Viewport 和 Scrollable 的实现都是很通用的,所以一般在 Flutter 里要实现不同的滑动列表,就是通过自定义和组合不同的 Sliver 来完成布局
ListView
使用的是 SliverFixedExtentList
或者SliverList
;
GridView
使用的是 SliverGrid
;
PageView
使用的是 SliverFillViewport
;
ListView
是由 Viewport
+ Scrollable
和一个RenderSliver
组成,所以在ListView
里只会有一个 RenderSliver
而不是多个,想使用多个 RenderSliver
需要使用 CustomScrollView
。