1. 程式人生 > >Android程序員的Flutter學習筆記

Android程序員的Flutter學習筆記

theme oid get http oss them web ali mat

Android程序員的Flutter學習筆記
作為忠實與較資深的Android汪, 最近抽出了一些時間研究了一下Google的親兒子Flutter, 尚屬皮毛, 只能算是個簡單的記錄吧.

Google自2017年第一次提出Flutter, 到2018年Beta, 再加之RN的各種風波與問題, 使得Flutter的熱度不斷上升, 國內不少公司都公布Flutter在其產品中的應用, 如美團, 閑魚等.

前言
Flutter作為跨平臺框架, 常常被人拿出來與React Native, 以及Xamarin進行對比, 除了大家都是跨平臺框架之外且能達到近乎Native的體驗之外, Flutter與這兩者的原理大不相同.

讓我們來看看這三者的結構圖吧.
技術分享圖片技術分享圖片技術分享圖片

可能有一些復雜, 咱大致解釋一下.

React Native跟Xamarin都是基於mapping native代碼來實現所謂的Native體驗的框架, 只是RN基於JS引擎 + Bridge與native打交道, 並且在運行時進行綁定, 而Xamarin是基於微軟的基於Linux的C#虛擬機mono + JNI與native進行通信.

這裏Android與iOS還是有差別的, 如RN在iOS上JS引擎不支持JIT, 會一定程度影響效率, Xamarin在iOS上可以直接編譯成iOS平臺可以執行的程序, 所以在實際運行起來的性能是一樣的, 唯一的差別就是微軟得更快的支持API同步.

對於Flutter來說, 由於他的渲染引擎使用了Skia直繪, 加上基於C++的Dart引擎, 所以在不同平臺上沒有差別, 加之其實現了Android Material Design與iOS Cupertino兩套UI組件, 所以即便是自繪組件, 看起來還是跟原生的一個樣子.

通過對三種跨平臺引擎的大致了解, 我們可以看出來, 他們都達到了一定程度的Native體驗, 然則各自都有一定的性能損耗, 比如RN的JS引擎加載JS, 以及Bridge通信的損耗, Xamarin Mono虛擬機與Java通信的損耗, 以及Flutter Skia渲染與Native Android渲染的差異等.

Flutter筆記

如何啟動一個app
Android需要在Manfest裏面指定帶有MAIN action與LAUNCHER category的Activity聲明, 而Flutter只需要一行.

void main() => runApp(MyApp());
其中MyApp就是一個普通的Widgets(View).

View vs Widgets
Flutter沒有View, 與之對應的是Widget, 並且分為StatelessWidgets與StatefulWidgets, 前者是個靜態View, 後者是動態通過Data來更新的View.

Stateless

Text(
‘I like Flutter!‘,
);
Stateful
class StatefulText extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TextState();
}

class _TextState extends State<StatefulText> {
  // Default placeholder text
  String textToShow = "I Like Flutter";

  void _updateText() {
    setState(() {
      // update the text
      textToShow = "Flutter is Awesome!";
    });
  }
  @override
  Widget build(BuildContext context) {
      ...invoke _updateText
  }
}

實際上是因為StatefulWidgets通過調用State的setState方法來觸發整個Widgets樹的重繪, 並且在重繪之前會調用傳進去的(){ ... }block.

怎麽寫Layout, XML到哪裏去了.
實際上Flutter沒有xml了, 並且是通過Widgets的嵌套來實現一個布局的.

如:

Center是一個可以把子View放置在中央的容器.
Row對應的就是LinearLayout + Horizontal,?Column對應的就是LinearLayout + Vertical, 他們都具備一個屬性叫做crossAxisAlignment, 有點類似gravity, 來控制子View相對於父View的位置.
Expanded支持一個類似weight的屬性, 叫flex.
Container是一個具有decoration屬性的容器, 可以用來控制背景色, border, margin等等.
Stack有點像是一個特殊的RelatetiveLayout或者ConstraintLayout,?children屬性指定了它的子View, 第一個是Base View,?alignment屬性指定了後面的子View相對於BaseView的位置, 如alignment: const Alignment(0.6, 0.6)指定了位於BaseView右下角的位置.
ListTile是一個特殊的ListItem, 有三個屬性, 分別是左邊的Icon (leading), 文字 (title), 以及右邊的Icon (trailing).
還有諸如ListView,?GridView,?Card等等比較熟悉的Widgets.
另外有一個類似於我們Activity的Widgets:

叫做MaterialApp, 可以指定theme,?title, 以及子View?home, 還有更重要的頁面跳轉routes.

MaterialApp(
      title: ‘Welcome to Flutter‘,
      home: ...,
      routes: <String, WidgetBuilder> ...,
      theme: ThemeData(
        primaryColor: Colors.white
      ),
    )

還有一個類似於Fragment的:

叫做Scaffold, 中文意思是腳手架, 它包含一個appBar (ActionBar)與一個body, appBar可以指定title與actions (類似於action button的點擊事件).
Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[...],
),
body: ...,
)
如何從父View中Remove一個元素
答案是沒有... 因為在Flutter看來, Widgets的樹結構是不可以被更改的, 但是如果想更改, 則是通過StatefulWidgets的方法, 通過setState來更改Data, 觸發Widgets重繪, 從而替換掉之前的Widgets.

喜歡畫Canvas的同學怎麽辦?
Flutter同樣支持,?CustomPaint作為一個 Widgets就支持傳入一個實現CustomPainter抽象類的參數, 而CustomPainter的抽象方法也類似於Android View的onDraw

void paint(Canvas canvas, Size size)

bool shouldRepaint(CustomPainter oldDelegate)

如何自定義View
不用繼承, 而使用類似Android ViewGroup的辦法, 通過組合(composing)與封裝的方法來實現, 通過小Widgets組合成需要的新Widgets.

頁面跳轉怎麽辦, 四大組件之一的Intent跑哪裏去了
貌似在講類似於Activity的MaterialApp的時候劇透了...

就是使用Navigator與Routes來實現界面跳轉, 實際上是整個Widgets的替換.

routes: <String, WidgetBuilder> {
      ‘/a‘: (BuildContext context) => MyPage(title: ‘page A‘),
      ‘/b‘: (BuildContext context) => MyPage(title: ‘page B‘),
      ‘/c‘: (BuildContext context) => MyPage(title: ‘page C‘),
    }

Navigator.of(context).pushNamed(‘/b‘);

如何處理外部的Intent
實際上還是需要在Flutter App的Android殼子中註冊這個filter, 然後在FlutterActivity中拿到存下來,

FlutterView初始化後再通過Bridge, 官方叫MethodChannel從Java裏獲取,進行下一步邏輯.

可以看個簡單的例子.

new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(
      new MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall call, MethodChannel.Result result) {
          if (call.method.contentEquals("getSharedText")) {
            result.success(sharedText);
            sharedText = null;
          }
        }
      });

getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
**常用的startActivityForResult怎麽辦**
```.
這個Flutter有完全對應的辦法, 而且用起來很方便, 結合之前說的頁面跳轉:

Map xxx = await Navigator.of(context).pushNamed(‘/xxx‘);

Navigator.of(context).pop({xxx});

**異步怎麽辦, runOnUiThread()哪裏去了**
Flutter有點像JS, 是一個單線程模式, 所以只是通過模擬來構建簡單的異步, 關鍵字就是類似於kotlin coroutines一樣, 通過await+async來處理.

如:

loadData() async {
response = await http.get(xxx);
setState(() {xxx});
}

但是由於它的單線程, 所以無法做很長的阻塞操作, 像http請求的延遲正常情況可能都是毫秒級的, 但是數據的處理等, 可能就得秒級了.

這也是RN在線程方面的做android程序的一個痛點, Flutter采用了比較容易想到的曲線救國的辦法, 提供了一個叫Isolate的對象, 它實際是一個基於socket的數據通道, 相當於把數據放在一個獨立的進程進行處理, 然後再通過socket發送回程序進程, 還記得進程間通信辦法之一的管道嗎...

?

**Flutter 替代OkHttp的網絡庫**
自帶了http庫, 直接http.get(url), 在線程部分的代碼實例裏也有涉及.

通過類似gradle的文件pubspec.yaml引入.

dependencies:
...
http: ^0.12

^表示不升大版本, 並取最新版本, 比gradle的+要範圍更小.

**常見的LCE(Loading Content Error)裏面的Loading怎麽show**
Flutter有一個widget叫做ProgressIndicator, 比如我們期望有一個轉圈圈的Loading界面在數據加載出來之前.

我們就可以通過StatefulWidgets, 根據數據, 或者List Widgets的個數 (如果是顯示一個List的話)來判斷是否顯示Loading, 使用子類CircularProgressIndicator, 來替換頁面的Widgets.

當然也是通過setState(() {...})來觸發界面刷新的, 可以在initState()內觸發加載數據的異步操作.

**不同分辨率的圖片資源怎麽放**
這個有點像iOS了, 即有1x,2x,3x:

images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image

不一樣的一點還需要添加到類似gradle的文件pubspec.yaml裏.

assets:
images/my_icon.jpeg
**字符串怎麽存儲**
Flutter沒有像Android的string.xml的東西, 目前來說最好的就就是存成靜態字符串.

class Strings {
static String welcomeMessage = "Welcome To Flutter";
}

Text(Strings.welcomeMessage)

**Gradle變成什麽了**
前面說網絡庫, 圖片資源的時候提到過, 提供了一個叫pubspec.yaml的文件

**Fragment與Activity呢?**
之前做過類比, 如MaterialApp有點類似於Activity, 而Scaffold都點類似Fragment, 實際上他們兩個都是Flutter的Widgets, 也就是說其實只有View的概念了.

**還有生命周期嗎?**
Flutter有一個叫做WidgetsBinding的可以提供類似生命周期的回調.

四種狀態inactive?(iOS專用),?paused(相當於onPause, 退後臺),?resumed(相當於onPostResume, 到前臺),?suspending(android專用, 相當於onStop).

一般在StatefulWidgets的State中註冊與反註冊.

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

**ScrollView vs ListView**
Flutter沒有ScrollView, 合並到了ListView, 通過ListView.builder創建的ListView提供了View復用的邏輯.

ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return Text(xxx);
}))

其中itemBuilder有點像Android ListView的getView, 官方文檔說它會自動回收Element給你, 但是事實上每次你都需要根據position生成新的Widgets, 所以呢應該是Flutter在內部回收了之前的Widgets並在你重新創建的時候又用上了.

BTW, 通過ListView構造來顯示就不具備這種特性, 所以大量數據需要用Builder.

**Flutter橫豎屏怎麽玩.**
因為它實際上還是借助了Android程序的殼子, 所以如果AndroidManifect定義了android:configChanges="orientation|screenSize", 則Flutter會自己hanlde.

**怎麽處理Gesture**
Flutter提供了GestureDetector, 它相當於一個Container, 將我們期望接收手勢的Widgets放進去, 再實現事件回調就行了.

GestureDetector(
child: FlutterLogo(
size: 200.0,
),
onTap: () {
print("tap");
},
)

它同樣支持其他的手勢, 如onDoubleTap等等等.

**字體怎麽弄**
首先需要在pubspec.yaml裏面配置需要的字體庫:

fonts:

  • family: MyCustomFont
    fonts:
    • asset: fonts/MyCustomFont.ttf
    • style: italic
      
      然後在Text的style屬性進行配置.
Text(
        ‘This is a custom font text‘,
        style: TextStyle(fontFamily: ‘MyCustomFont‘),
      )

Hint哪裏去了, 錯誤信息怎麽輸出
對於輸入框的Hint基本一致, 可能就是換了個名字, 一看便知.


TextField(
    decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
  )

總結
Flutter在視圖渲染上另辟蹊徑, 性能優勢凸顯, 在跨平臺框架屬於一匹黑馬, 又有Google撐腰, 值得在Mobile勤耕多年的同學入手.

由於作者曾經從事過2年的Webkit開發工作, 拜讀了Flutter的渲染模式, 很像是Webkit/Chrome/Blink的思路, 通過查證, 起草者確實有大批同樣的人, 如果你還沒有入坑RN, 或許Flutter可以作為跨平臺方案學習的首選哦.

同樣Google自己也有很多Plugin去支持更多擴展功能, 如GPS, Camera, SharePreference, Database. 還例如Firebase這種親兒子級的服務也是全面支持Flutter.

當然也可以自己去開發需要的Plugin來適配需要的功能, 基於的技術就是上面有提的MethodChannel, NDK的支持也是同樣的道理.

Android程序員的Flutter學習筆記