在我们深入学习 Flutter 之前,对 Flutter 特性有个清晰的认识我认为是非常有必要的,这会让我们后面的学习更加透彻
尤其是这几天我被拉去了一个卖 Flutter 学习课程的公开课里,前面的内容很不错,在开始 Flutter 课程之前,先把 Flutter 的特性说上这么一说,我觉得这样学习的话效果会很不错。不能一上来就直接学,得先对要学习的内容有个深刻的认识,这样才好在学习的时候有更深入,更正确的认识
Flutter 最显著的特性就 2 个:
跨平台 - 是指我们可以通过同样的 Flutter 代码在 android,ios 2 端实现同样 UI 效果的 app ,这里就要说明了 Flutter 实质上实现的 UI 跨平台,涉及到不同平台的巨大差异,是不可能使用同一套代码在多端实现相同的效果的,未来随着 Fuchsia 系统的上线,Flutter 的运行环境还会加上 pc,物联网,web 端,多端之间的差异更是会拉大,想着一套代码运行那是没戏的,只能是通过 Flutter 代码实现 UI 层级的多端运行, Flutter 代码取调用各端本地实现的功能 lib
Fuchsia 已经可以在 PC 运行了我们知道之前 FaceBook 的 RN 也是号称:"一处编写,处处使用",后来不像宣城的这么好事,然后改成了 "一处学习,处处使用",再然后就没然后啦,18年 FaceBook 停更 RN 了,也就是放弃了。RN 的是使用 JS 语言编写代码,然后由 RN 解释器在本地映射生成 native 的 view 已实现显示的,android 吧本身 UI 性能就不怎么样,要不网上怎么会有一大票铺天盖地的 UI 性能优化文章呢,RN 在 android 之上再次加上了 view 解释器和生成器,无形这又是增加了不少任务,所以 RN 的性能优化了好久,最后还是不理想,无奈放弃,这也是有原因的,其实最大的原因就是 Flutter 的出现,Flutter 的性能相比 RN 有太大的优势,FaceBook 也就不在 RN 身上在费事了,注定是打不过 Flutter 的,再说 RN 的坑太多,大伙都不用了,没见阿里爸爸都写了一篇 RN 从入门到放弃吗
哈哈这不正巧 FaceBook 正式回应了 Flutter 了吗,在专访中面对直白的问题,FaceBook 没有做正面回答就是最好的答案,呵呵,FaceBook 自己都信心不足,说什么 RN 和 Flutter 不是一个类型的,没有可比性,这不是纯扯淡嘛,详见:
都说 Flutter 性能好,为啥呢,这还得从 Flutter 的架构看,其实很简单
Flutter 的架构图Engine C++ 内嵌层包括: Skia | Dart | Text
- Skia - 是开源的二维图形库,提供了适用于多种软硬件平台的通用API。其已作为 Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS 等其他众多产品的图形引擎,支持平台还包括 Windows7 / macOS / iOS8 / Android4.1 / Ubuntu14.04
- Dart - 部分主要包括 : Dart Runtime,Garbage Collection(GC)
- Text - 文本渲染,其渲染层次如下:衍生自minikin的libtxt库(用于字体选择,分隔行)。HartBuzz用于字形选择和成型。Skia作为渲染/GPU后端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics来渲染字体。
重点看红圈部分,Flutter 是直接使用 Skia 2D 图形引擎来绘制的,不再是 RN 的那套映射 native 本地组件了,Flutter 做的比 native 更彻底,理论性能上甚至比 native 上还好
Flutter 怎么干的,Flutter 没有 xml 了,UI 组件直接写在代码里面,Flutter 生成的 android 项目只有 MainActivity 这一个页面, MainActivity 页面里面只有 FlutterView 这一个 view ,所有的 UI 组件全部由 FlutterView 直接在 canvas 上绘制,比 android 原生还利落
android 原生得经过 xml(Dom 解析) -> layoutInflater 反射出 view 视图树来 -> rootView 的 measu,layout,draw -> 系统驱动 Skia 2D 绘图引擎来绘制图像
Flutter 没有了 xml ,也就不用 Dom 解析了,直接通过 canvas 驱动 Skia 2D 绘图引擎来绘制,FlutterView 渲染 UI 也比 android 强很多,不再是 android 那样从 rootView 一层一层的反复计算,定位再绘制。Flutter 使用统一的 UI 模型:widget,在更新 widget 的时候,采用类似于 recycleview DiffUtils 那样的 view 复用机制复用 widget 对象,无用的 widget 能复用就复用,更更新数据就更新数据,这样比实现更高效率的 UI 渲染。当然一切看优化,Flutter 要是优化到位,性能肯定比原生要好这是一定的了,可能还好很多
图不是很清晰,大家看个意思
Flutter 对比 android 绘制流程
通过对比,我想说 Flutter 真是太棒了,Google 通过 android 的锻炼已经对移动端足够了解,坑都趟的差不多了,所以作为下一代产品预热的 Flutter 天然的在技术上对 android NB 多了,以 android 现在的市场占有率,Google 的能力,Fuchsia 一出来包括硬件和软件在内的生态很快都能做起来,取代 android 也就是 2-3年的事
为了做在到多端的 UI 统一,Flutter 提供的 UI 组件在 android 和 ios 上分别了做了自行绘制,以保证式样相同
Flutter 在 android / ios 上 UI 对比_1 Flutter 在 android / ios 上 UI 对比_2
Flutter 在 Android 系统上使用 Skia 绘图引擎绘制图形,在 ios 上使用 JPEG 绘图引擎绘制图形,这些都是各端原生的,将来还会有 pc 端的,web 端的实现。可以看到 Flutter 思路的巧妙,简便,当然也是任务量巨大,所以 Flutter 才能在众多跨平台方案中脱颖而出,赢得宝座,这里我得再次膜拜 Google 大神
热重载 - 这个不要理解为热修复啊,不是这个,热重载指的是在开发阶段我们只要 Control + s 保存就能直接在设备上更新代码了
Flutter 在 deBug 阶段不需要编译,直接发送 Dart 文件差异包给链接的设备更新代码,因为没有 编译,所以也不用 打包,安装,这2个过程,可以做到秒级更新的水平,这比传统的 android 编译,打包,安装的过程快了不知道多少,真是一个天上一个地下,这个时候我想大家都会深深体验到:科技改变生活这句话含义的,有了这个优势,我们一天至少多了 20-30 分钟的工作时间
Flutter 渲染机制
Flutter 界面渲染过程分为三个阶段:布局、绘制、合成
布局和绘制在Flutter框架中完成,合成则交由引擎负责,至于采用合成图层的原因:由于直接交付给 GPU 多图层视图数据是低效率的,可能会重复绘制,所以还需要进行一步图层的合成,最后才交由引擎负责光栅化视图
- Widget 树 - 就是我们的UI组件树,但这个只是一种描述信息,渲染引擎是不认识的
- RenderObject 树 - 这才是渲染引擎真的认识的,我们需要将 Widget 转化为能用来渲染视图的 Render Object
这样 Flutter 就节约了类似 android 这样 layout xml DOM 解析操作的性能损耗与频繁进行局部 DOM 操作的矛盾
以前 android DOM 会在每一次元素更新到来之时渲染一次DOM,Flutter 会先汇总各个元素的更新情况,通过diff算法计算出与原来 DOM 树的差异,最后通过一次 DOM 更新解决
还有要注意一点,每一个 Flutter 实例会启动三个线程,分别是 UI 线程、GPU 线程和 IO 线程,所以复用一个 Flutter 实例会减少资源消耗
刚详细的 Flutter 渲染机制请看:纷争再起:Flutter-UI绘制解析
从源码看看 Flutter 怎么显示的
目前我这只能运行 Flutter 编译的 android app,这也是 android coder 关心的,其实从这里也能看出来 Flutter 如何跨平台的
1. Flutter main 入口
Flutter 项目的入口是 main 函数,也是整个 Flutter 项目执行的开始,这里看出来 Dart 受 java 影响还是很深的
Flutter main 函数
Flutter 在 android 项目里会把 Flutter 的 main 入口编译成 MainActivity
MainActivity 位置
而这个 MainActivity 则继承自 FlutterActivity
class MainActivity(): FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
看出来了吧,Flutter 就是这么在 android 中开始的,Flutter 编译的 android 项目只有这么一个 Activity 的,想不想 Google 的风格,16 年可以兴起一阵学习 Google Mail+ 单 Activity + 多 Fragment 的页面组合方式,当年我还追来着,后来发现 Fragment 栈管理真是难啊,这不单 Activity + 多轻量级 page 的思路在 Flutter 上又复活了,接着看,这个思路妥妥的
2. FlutterActivity
FlutterActivity 内部没有啥逻辑,不看也没事,是声明了 Provider, PluginRegistry, ViewFactory 3个接口,然后自己实现
- Provider | ViewFactory - 是提供 view 视图的
- PluginRegistry 着一连串方法可以看到是代理了 Activity N多生命周期函数
/// activity实现了三个接口
public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {
/// 视图提供类,一个方法getFlutterView
public interface Provider {
FlutterView getFlutterView();
}
/// 插件注册
public interface PluginRegistry {
/// 注册一个插件
PluginRegistry.Registrar registrarFor(String var1);
/// 是否有这个插件
boolean hasPlugin(String var1);
/// 插件发布值
<T> T valuePublishedByPlugin(String var1);
/// 下面开始为插件生命周期回调
public interface PluginRegistrantCallback {
void registerWith(PluginRegistry var1);
}
/// 显然是视图销毁的回调
public interface ViewDestroyListener {
boolean onViewDestroy(FlutterNativeView var1);
}
//// 不知道干啥,先留着
public interface UserLeaveHintListener {
void onUserLeaveHint();
}
//// activity生命周期回调之onNewIntent
public interface NewIntentListener {
boolean onNewIntent(Intent var1);
}
//// activity生命周期回调之onActivityResult
public interface ActivityResultListener {
boolean onActivityResult(int var1, int var2, Intent var3);
}
//// activity生命周期回调之onRequestPermissionsResult
public interface RequestPermissionsResultListener {
boolean onRequestPermissionsResult(int var1, String[] var2, int[] var3);
}
/// 插件的注册者,相当于插件的宿主。
public interface Registrar {
Activity activity();
Context context();
Context activeContext();
/// 这个BinaryMessager还不知道干啥
BinaryMessenger messenger();
/// TextureRegistry不清楚
TextureRegistry textures();
/// 获取视图
FlutterView view();
/// 寻找资源
String lookupKeyForAsset(String var1);
/// 寻找资源
String lookupKeyForAsset(String var1, String var2);
/// 发布值,与上面的valuePublishedByPlugin对应
PluginRegistry.Registrar publish(Object var1);
/// 增加生命周期回调
PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener var1);
/// 增加生命周期回调
PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener var1);
PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener var1);
/// 增加生命周期回调
PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener var1);
/// 增加生命周期回调
PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener var1);
}
}
/// 视图工厂
public interface ViewFactory {
/// 创建flutterView
FlutterView createFlutterView(Context var1);
//// 创建nativeview
FlutterNativeView createFlutterNativeView();
/// 暂时搞不清楚干啥的,先留着
boolean retainFlutterNativeView();
}
private final FlutterActivityEvents eventDelegate = delegate;
private final FlutterView.Provider viewProvider = delegate;
private final PluginRegistry pluginRegistry = delegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
eventDelegate.onCreate(savedInstanceState);
}
3. FlutterActivityDelegate
在 2 中我们能看到 FlutterActivity 把包括 onCreate 在内的所有生命周期函数都交给 eventDelegate 这个代理了, eventDelegate 的类型是 FlutterActivityEvents ,那么重点是我们看看 FlutterActivityEvents 的 onCreate 干啥了
public final class FlutterActivityDelegate implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry {
........
@Override
public void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
String[] args = getArgsFromIntent(activity.getIntent());
FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
flutterView = viewFactory.createFlutterView(activity);
if (flutterView == null) {
FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
flutterView = new FlutterView(activity, null, nativeView);
flutterView.setLayoutParams(matchParent);
activity.setContentView(flutterView);
launchView = createLaunchView();
if (launchView != null) {
addLaunchView();
}
}
if (loadIntent(activity.getIntent())) {
return;
}
String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
if (appBundlePath != null) {
runBundle(appBundlePath);
}
}
}
核心就是创建出 FlutterView 这个 view 对象,然后把 FlutterView setContentView 给 activity,这样就完成了从 Flitter 到 android 平台的显示,MainActivity 这个AC 可是只有 FlutterView 这一个 view 啊,Flutter 所有的 UI 组件都是由 FlutterView 使用 canvas 直接绘制的。Flutter 就是这样和 android 联系的,脱离了 android 传统的 xml 布局方式,节省了 xml dom 解析反射生成 viewTree 的操作
FlutterView 可以解答我们很多问题,而且是核心问题,从 FlutterView 我们可以看到 Flutter 的优越性
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
super(context, attrs);
Activity activity = (Activity) getContext();
if (nativeView == null) {
mNativeView = new FlutterNativeView(activity.getApplicationContext());
} else {
mNativeView = nativeView;
}
mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
assertAttached();
mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
assertAttached();
mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
assertAttached();
mNativeView.getFlutterJNI().onSurfaceDestroyed();
}
};
}
FlutterView 继承自 SurfaceView ,那么也就是说明 UI 绘制不是由 android UI 线程完成,而是完全完全由 Flutter 自己单干,这好啊,android UI 线程事太多了,把任务分解给别人干会让系统快很多的,而且 SurfaceView 是双缓存的,会缓存上一帧和当前帧,android 同步页面信号时若是 UI 线程还没把当前帧算出来,那么就会放弃当前帧的渲染,这就造成了丢帧,丢 3帧 用户就会感觉到明显的卡顿了,尤其是在动画时有有味明显。SurfaceView 不然,SurfaceView 有自己独立的 surface 显存区域,自己绘制自己, 双缓存,页面同步信号来是我只要把计算好的当前帧发给系统就行了,而不会管我现在是不是在计算下一帧
代码里 FlutterView 的绘制实际交给了 FlutterNativeView.getFlutterJNI() - FlutterJNI,我们去看看 FlutterJNI
public class FlutterJNI {
@UiThread
public static native boolean nativeGetIsSoftwareRenderingEnabled();
@UiThread
public static native String nativeGetObservatoryUri();
private Long nativePlatformViewId;
private FlutterRenderer.RenderSurface renderSurface;
private AccessibilityDelegate accessibilityDelegate;
private PlatformMessageHandler platformMessageHandler;
@UiThread
public void onSurfaceCreated(@NonNull Surface surface) {
ensureAttachedToNative();
nativeSurfaceCreated(nativePlatformViewId, surface);
}
private native void nativeSurfaceCreated(long nativePlatformViewId, Surface surface);
@UiThread
public void onSurfaceChanged(int width, int height) {
ensureAttachedToNative();
nativeSurfaceChanged(nativePlatformViewId, width, height);
}
private native void nativeSurfaceChanged(long nativePlatformViewId, int width, int height);
@UiThread
public void onSurfaceDestroyed() {
ensureAttachedToNative();
nativeSurfaceDestroyed(nativePlatformViewId);
}
private native void nativeSurfaceDestroyed(long nativePlatformViewId);
}
FlutterJNI 里面有大量的 native 方法,都是和底层打交道的,期中 FlutterRenderer.RenderSurface renderSurface 就是负责显示的,FlutterRenderer 是 Flutter 负责渲染的组件,剩下的大家自己取看吧,基本上这就解释清楚了 Flutter 为啥可以跨平台而 UI 样式相同,因为纯是自己画的,脱离原生平台组件
Flutter 内存/ GC 管理
以下内容来源于:闲鱼技术团队,参考文章: Flutter 内存机制梳理
Flutter 使用 Dart 语言开发,所以和 java JVM 虚拟机一样需要 Dart 的运行环境 VM。和Android ART 一样,Flutter 也对Dart 源码做了 AOT 编译,直接将 Dart 源码编译成了本地字节码,没有了解释执行的过程,提升执行性能
这里我们关注的是 Dart VM 内存分配和 GC 的相关部分。和 Java 不同,Dart 的线程 Isolate 是不共享内存的,拥有各自独立的堆(Heap) 和栈 (Stack) ,并且是各自独立 GC 的,彼此之间通过消息通道来通信。Dart天然不存在数据竞争和变量状态同步的问题,整个 Flutter Framework Widget 的渲染过程都运行在一个 isolate 中
Dart 内存管理GC 部分,Dart 和 java 是相同的,同样在堆内存中分为:新生代 / 老年代,老年代里分 from / to 2快区域,进行整块内存地址的来回复制,删除
可以看到,Dart VM 借鉴了很多 JVM 的思路,Dart 中产生内存泄露的方式也和 Java 类似,Java 中很多排查内存泄露的思路和防止内存泄露的编程方法应该也可以借鉴过来
Android 将内存分为 JVM 虚拟机内存 和 Native 内存,系统对 JVM 虚拟机内存有一个上限,到达上限就会触发 OOM 异常,而对 Native 内存的使用没有太严格的限制,现在的手机内存都很大,一般有较大的Native内存富余,这里我们来看下 Android 和 Flutter 对 bitmap 存储的处理
-
android - 6.0 / 7.0 把 bitmap 储存在 JVM 虚拟机内存中,8.0 开始把 bitmap 储存在 Native 内存中
android 6.0
android 8.0 -
Flutter - 既没用 JVM 虚拟机内存,也没用 Native 内存,而是 Graphics 内存,我们可以叫显存,是 GPU 使用的内存部分,这样效率上要块不少,因为不用省了把 bitmap 传递给 GPU 的这一部
Flutter
官方对 Graphics 内存的解释
不亏是 Google 大神,就是厉害啊
Flutter 编译模式
- JIT - Just-in-time,动态(即时)编译,边运行边编译。开发阶段使用,采用 JIT 模式,这样就避免了每次改动都要进行编译,实现极大的节省了开发时间
- AOT - Ahead Of Time,指运行前编译。发布阶段使用,通过 AOT 生成高效的 ARM 代码以保证应用性能,而JavaScript则不具有这个能力
Flutter 有 3种开发模式:debug | release | profile ,他们之间的差异比 android 中不同编译模式的差距大
- Debug 模式 - 使用 JIT 模式编译 Dart 文件,也是我们上面说的热重载,此模式支持所有的调试手段,但是在启动时会变慢,对性能有影响
- Release 模式 - 使用 AOT 模式编译 Dart 文件,只支持真机,不支持模拟器,不支持各种调试手段,对包大小做了优化,此模式性能最优
- Profile 模式 - 不支持模拟器,因为模拟器并不代表真实的性能,Profile 和 Release 相同,区别是 Profile 模式支持调试工具,
事实上 flutter 下的 iOS / Android 工程本质上依然是一个标准的 iOS / Android 工程,flutter只是通过在 BuildPhase 中添加 shell 来生成和嵌入 App.framework 和 Flutter.framework(iOS) , 通过 gradle 来添加 flutter.jar 和vm / isolate_snapshot_data / instr(Android) 来将 Flutter 相关代码编译和嵌入原生App而已
更具体的内容详见:深入理解flutter的编译原理与优化
Flutter 项目结构
- android - android 平台相关代码
- ios - ios 平台相关代码
- lib - flutter相关代码,我们主要编写的代码就在这个文件夹
- test - 用于存放测试代码
- pubspec.yaml - 配置文件,一般存放一些第三方的依赖。主要是用Dart的pub包管理工具
最后
最后了,我们来梳理下 Flutter 的优点,摘自:浅谈flutter的优点与缺点
Flutter 优点:
-
性能强大,流畅
Flutter对比weex和react native相比,性能的强大是有目共睹的。基于dom树渲染原生组件,很难与直接在原生视图上绘图比肩性能,Google作为一个轮子大厂,直接在两个平台上重写了各自的UIKit,对接到平台底层,减少UI层的多层转换,UI性能可以比肩原生,这个优势在滑动和播放动画时尤为明显。 -
UI跨平台稳定
Google直接在两个平台上在底层重写了UIKit,不依赖于Css等外部解释器,几乎不存在UI表达不理想,渲染不正常的情况,可以获得非常稳定的UI表达效果。Css换个浏览器就有不同的表现,基于Css的跨平台框架很难获得稳定的UI表现。
可选静态的语言,语言特性优秀 -
路由设计优秀
Flutter的路由传值非常方便,push一个路由,会返回一个Future对象(也就是Promise对象),使用await或者.then就可以在目标路由pop,回到当前页面时收到返回值。这个反向传值的设计基本是甩了微信小程序一条街了。弹出dialog等一些操作也是使用的路由方法,几乎不用担心出现传值困难 -
单例模式
Flutter支持单例模式,单例模式的实现也非常简单。单例模式很好的解决了一些问题。相比之下,js的单例则并不是一个真正的单例,或者说不是一个简单的单例,这也是受限于js所运行的环境。单例模式并不总是合理的,容易被滥用。但是在App的初期开发中,往往一个容易实现的单例可以帮助我们快速完成一些逻辑的搭建。 -
优秀的动画设计
Flutter的动画简单到不可思议,动画对象会根据屏幕刷新率每秒产生很多个(一般是60个)浮点数,只需要将一个组件属性通过补间(Tween)关联到动画对象上,Flutter会确保在每一帧渲染正确的组件,从而形成连贯的动画。这种十分暴力的操作在Flutter上却看不到明显的卡顿,这也是Flutter的一个魔力所在。相比之下其他跨平台框架几乎不能设计动画……往往会遭遇非常严重的性能问题。
Flutter 缺点:
-
假装跨平台,躲不开原生代码
这是最大的问题,跨平台框架说白了就是UI跨平台,最后还是在原生平台运行,本来两个平台就有天壤之别,一套代码就想吃掉iOS和Android在实际应用之中其实根本就不现实。Flutter具有与原生代码互相调用的能力固然非常科学,但是问题反而显得更加明显——我一个前端工程师上哪里去知道什么是UIViewController,什么是Activity呢?我要是双端都熟悉,学习Flutter就显得很没有必要。这是一个很矛盾的点,特别是在团队里,只有几个前端突然想学Flutter,是绝对做不来大项目的,如果有原生开发者,那就没必要搞Flutter了。 -
组合而不是继承的思路
Flutter提倡“组合”,而不是“继承”。在iOS开发中,我们经常会继承UIView,重写UIView的某个生命周期函数,再添加一些方法和属性,来完成一个自定义的View。但是在Flutter中这些都是不可能的——属性都是final的,例如你继承了了一个Container,你是不能在它的生命周期中修改他的属性的。你始终需要嵌套组合几种Widget,例如Row,Container,ListView等Widget。这种方法非常不符合直觉,初学时很难想明白如何构建一个完整的组件。 -
糟糕的UI控件API
虽然google尽可能的让我们通过构造函数定制化Widget,但是也难免有遗漏的。例如,又一次我想修改一个Appbar的高度,居然没有找到关于高度的属性,通过阅读源码发现,高度是写死(const)的。上文已经说过,无法通过生命周期来改变组件属性,自己写Appbar显得非常没必要,毕竟我还是想使用Appbar的各种方便的功能。最后我只能把他的源码全部复制出来,直接修改高度来使用。初学框架,和一些初级开发者是不可能有迅速阅读源码的能力的(作为框架也不应该产生如此问题)。一些定制化的UI的Api设计经常有缺失,好在我已经基本习惯了。除了Appbar这种复杂的组件,自己写一个小组件也并不费事。
网友评论