給Android開發者的Flutter指南 (下) [翻譯]

horseLai發表於2019-08-20

官方英文原文: flutter.io/flutter-for…

說明:此文上接 給Android開發者的Flutter指南(上)

四、工程結構與資源

1. 在哪放置不同解析度(resolution-dependent)的圖片檔案?

Android中,resourcesassets是兩個獨立的資料夾,而在Flutter中,只存在assets,所有放在Androidres/drawable-*資料夾中的檔案全都放在Flutter中的assets資料夾中。

Flutterios一樣遵循簡單的基於密度(density-base)的格式,assets包含1.0x2.0x3.0x或者更高乘數,Flutter中並沒有dp這一說,而是使用與裝置無關的邏輯畫素,在devicePixelRatio 中描述了單個邏輯畫素與物理畫素的關係。

對應於Android密度的關係如下:

Android density qualifier Flutter pixel ratio
ldpi 0.75x
mdpi 1.0x
hdpi 1.5x
xhdpi 2.0x
xxhdpi 3.0x
xxxhdpi 4.0x

assets可以存在與任意資料夾中,Flutter沒有規定資料夾結構,因此即使你將assets放在與pubspec.yaml相同的位置,Flutter也能正確的讀取到。

Flutter 1.0 beta2以前,在Flutter中定義的assets不能被本地層(native層)訪問,同理,本地層的assetsresources檔案也不能被Flutter訪問,因為它們存在於分立的資料夾中。

而從Flutter 1.0 beta2開始,assets儲存於本地層的assets資料夾中,且可以被本地層通過AssetManager訪問,但是Flutter依然不能訪問本地層的resourcesassets

val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")
複製程式碼

如果向Flutter工程中新增一個叫做my_icon.png的圖片資源,比方說,把它放到一個叫做Images的資料夾中(名字是任意的),那麼我們應該把1.0x的基礎圖片放到Images的根目錄,而其他大小,比如2.0x3.0x等大小的圖片分別放在Images中名字為2.0x3.0x的子資料夾中,如下示例路徑:

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
複製程式碼

接著在pubspec.yaml檔案中宣告這些圖片資源:

assets:
 - images/my_icon.jpeg
複製程式碼

接著就可以通過AssetImage訪問圖片資源了:

return AssetImage("images/a_dot_burr.jpeg");
複製程式碼

或者直接在Image控制元件中使用:

@override
Widget build(BuildContext context) {
  return Image.asset("images/my_image.png");
}
複製程式碼

2. 在哪儲存strings字串資源?怎樣處理本地化?

目前Flutter沒有像系統宣告字串資源那樣的形式,因此當前最佳方式就是將字串宣告成static形式,然後儲存在一個特定的類中,之後都從這個類中獲取,如下示例:

class Strings {
  static String welcomeMessage = "Welcome To Flutter";
}
複製程式碼

然後在程式碼中這樣呼叫這些字串資源:

Text(Strings.welcomeMessage)
複製程式碼

FlutterAndroid中的輔助功能有了基礎支援,目前工作還在進行中。開發者可以通過檢視intl package來獲取關於國際化和本地化的資訊。

3. 對應於Gradle檔案的是啥?怎麼新增依賴?

Android中,依賴新增在gradle構建指令碼中,而Flutter則是使用Dart自己的構建系統和Pub包管理器,Flutter的構建工具會將本地AndroidIOS的構建工作委託到它們各自的構建系統。

gradle檔案在Flutter工程目錄的android資料夾下,只有需要針對單個平臺新增本地依賴時才新增到gradle,其他普通場景直接在pubspec.yaml新增外部依賴就好了。找包?上 Pub

五、 Activity 與 fragment

Note: 你幾乎不會想讓Android因為Flutter應用而重啟activity,因為它違背了Android文件中的提出的建議,因此例如需要支援分屏,那麼也需要新增screenLayoutdensity

1. Flutter中與activityfragment相對應的是啥?

Android中,activity代表了使用者的單個焦點所在,而Fragment則代表使用者互動及介面的一個行為或者說一個部分。Fragment可以模組化你的程式碼,用來為大屏裝置組合出複雜的使用者互動介面、以及比例化應用UI。在Flutter中,這兩者的概念都彙集到在Widget

正如在Intent部分所提到的,在Flutter中,Widget就代表著螢幕,因為在Flutter中萬物皆Widget。我們使用Navigator來切換Route,而Route代表著不同螢幕或頁面、亦或只是不同狀態、或是相同資料的渲染效果。

2. 如何監聽Android中activity的生命週期事件?

Android中,我們會複寫activity中的生命週期方法,或者在Application中註冊ActivityLifecycleCallbacks,而在Flutter中,並沒有這個概念,但是我們可以通過給WidgetBinding觀察者下個鉤子(hook)來監聽生命週期事件,然後監聽didChangeAppLifecycleState()的變化事件,其生命週期事件如下:

  • inactive: 應用處於非活動狀態,此時不在接收使用者輸入。這個事件只在IOS中有效,因為在Android中沒有與這個狀態相對映的事件。
  • paused: 當前應用對使用者可見,但不在響應使用者輸入,且執行在後臺。等同於Android中的onPause
  • resumed: 應用可見且正在響應使用者輸入。等同於Android中的onPostResume()
  • suspending: 此時應用掛起了。等同於Android中的onStop;不會觸發IOS上的事件,因為沒有與之對映的狀態事件。

關於這些狀態的更多細節,請檢視 AppLifecycleStatus documentation..

你可能注意到了,只有那麼幾個可用的Android生命週期事件。這是因為FlutterActivity已經捕獲了幾乎所有的生命週期事件,並將它們傳送到了Flutter引擎中,然後很多事件都被它遮蔽掉了。Flutter會管理引擎的啟動和關閉動作,因而大多數情況下我們都沒多大必要在Flutter層監聽activity生命週期事件。如果要監聽或者釋放本地層的資源,那麼可以在本地層以任何頻率進行。

以下示例描述瞭如何監聽Activity中的生命週期事件:

import 'package:flutter/widgets.dart';

class LifecycleWatcher extends StatefulWidget {
  @override
  _LifecycleWatcherState createState() => _LifecycleWatcherState();
}

class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
  AppLifecycleState _lastLifecycleState;

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

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecycleState = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_lastLifecycleState == null)
      return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);

    return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
        textDirection: TextDirection.ltr);
  }
}

void main() {
  runApp(Center(child: LifecycleWatcher()));
}
複製程式碼

六、佈局

1. 對應於LinearLayout的是啥?

Android中,LinearLayout用於橫向和縱向佈局控制元件,而在Flutter中則是使用RowColumn控制元件來實現與之相同的行為。

如下示例,當佈局中子控制元件重複利用率比較高時,使用這種容器控制元件就很方便了:

@override
Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
複製程式碼
@override
Widget build(BuildContext context) {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Column One'),
      Text('Column Two'),
      Text('Column Three'),
      Text('Column Four'),
    ],
  );
}
複製程式碼

更多線性佈局細節,請看這裡 Flutter For Android Developers : How to design LinearLayout in Flutter?.

2. 對應於RelativeLayout的是啥?

Flutter實現與之相同效果的方法很少。可以通過組合ColumnRowStack控制元件來實現類似效果,也可以通過控制元件的構造器指定其子控制元件在它內部的佈局規則來實現。

關於如何構建一個RelativeLayout,可以檢視這裡 StackOverflow.

3. 對應於ScrollView的是啥?

Flutter中,實現ScrollVIew的最簡單方式就是使用LsitView。這看起來好像有點誇張,但是在Flutter中,ListView控制元件既是Android中的ScrollView,也是ListView

@override
Widget build(BuildContext context) {
  return ListView(
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
複製程式碼

4. 在Flutter中如何處理螢幕旋轉?

AndroidManifest.xml中新增如下配置即可:

android:configChanges="orientation|screenSize"
複製程式碼

七、檢測手勢與觸控事件處理

1. 如何為控制元件新增 OnClick 事件監聽器?Android中,是通過呼叫ViewsetOnClickListener方法繫結監聽器的,而在Flutter中可以有以下兩種新增觸控事件監聽器的方式:

  • 如果控制元件支援事件檢測,那麼可以給它傳入一個函式用以處理這個事件。比如,RaisedButton包含一個onPressd引數:
@override
Widget build(BuildContext context) {
  return RaisedButton(
      onPressed: () {
        print("click");
      },
      child: Text("Button"));
}
複製程式碼
  • 如果控制元件並不支援事件檢測,那麼可以將這個控制元件包裹在GestureDetector中,然後傳入一個函式給onTap引數:
class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: GestureDetector(
        child: FlutterLogo(
          size: 200.0,
        ),
        onTap: () {
          print("tap");
        },
      ),
    ));
  }
}
複製程式碼

2. 如何處理控制元件中的其他手勢?

使用GestureDetector可以監聽大量手勢,例如:

  • 單擊(Tab)

    • onTabDown: 觸發單擊事件的指標已經開始與螢幕在特定點上進行聯絡
    • onTapUp: 觸發單擊事件的指標停止與螢幕在特定點上的聯絡
    • onTap: 形成了單擊事件
    • onTapCancel: 觸發時會導致之前觸發onTabDown的指標無法形成單擊事件
  • 雙擊(Double Tab)

    • onDoubleTab: 使用者在螢幕的同一個點上連續快速點選了兩次
  • 長按Long Press

    • onLongPress: 指標持續在同一個位置上與螢幕進行了一段事時間的聯絡。
  • 垂直拖動(Vertical drag)

    • onVerticalDragStart: 指標已經和螢幕聯絡,並且可能開始垂直移動。
    • onVerticalDragUpdate: 正在和螢幕聯絡的指標已經開始在垂直方向上進行移動。
    • onVerticalDragEnd: 之前與螢幕進行聯絡且在垂直方向移動的指標,現在已經不需要再與螢幕聯絡了,並且在停止聯絡的瞬間,指標依然以一定的速度移動。
  • 水平拖動(Horizontal drag)(請參考上面的垂直拖動)

    • onHorizontalDragStart
    • onHorizontalDragUpdate
    • onHorizontalDragEnd

下面示例描述了在使用GestureDetector雙擊Flutter logo時,logo旋轉的效果:

AnimationController controller;
CurvedAnimation curve;

@override
void initState() {
  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
  curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
          child: GestureDetector(
            child: RotationTransition(
                turns: curve,
                child: FlutterLogo(
                  size: 200.0,
                )),
            onDoubleTap: () {
              if (controller.isCompleted) {
                controller.reverse();
              } else {
                controller.forward();
              }
            },
        ),
    ));
  }
}
複製程式碼

八、ListView 與 Adapter

1. 在Flutter中,替代ListView的是啥?

Flutter中,等效於ListView的是...ListView

對於AndroidListView,我們會建立一個介面卡(adapter)傳給ListView,然後ListView渲染這個介面卡返回的每一行資料。而我們必須確保每行資料最後都被我們回收,否則就可能會導致顯示錯亂和記憶體問題。

而因為Flutter的控制元件是不可變的,我們給ListView傳入一個集合的控制元件,然後Flutter就會自己確保滑動的流暢、快速了。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
    }
    return widgets;
  }
} 
複製程式碼

2. 我怎麼知道列表的哪個條目被點選了呢?Android中,ListView中包含了查詢點選了哪個條目的onItemClickListener監聽器,而在Flutter中,則是通過傳入列表的控制元件來處理觸控動作。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(GestureDetector(
        child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Text("Row $i")),
        onTap: () {  // 給列表中的每個控制元件新增一個點選事件監聽器
          print('row tapped');
        },
      ));
    }
    return widgets;
  }
}

複製程式碼

3. 如何動態更新ListView?

Android中是通過更新介面卡,然後呼叫notifyDataSetChanged來處理。

而在Flutter中,如果你是在setState()方法中更新控制元件集,那麼你會很快看到你的資料並沒有被更新,這是因為當setState()被呼叫時,Flutter渲染引擎會在控制元件樹中搜尋是否存在發生改變的東西,而當它找到了你的ListView,它會進行==檢查,然後判定這兩個ListView是相同的,因而不會發生任何改變,也就不會請求更新。

一個簡單更新ListView的方法就是,在setState()方法中建立一個新的List,然後將舊集合的資料拷貝過來。這一解決方案是簡單,但是不推薦在資料量大的時候使用,如下示例:

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: widgets),
    );
  }

  Widget getRow(int i) {
    return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}
複製程式碼

一個高效且有效的方式是通過ListView.Builder來建立ListView,這種方式在你的資料量巨大或者需要動態改變資料的情況下非常有用,這實質上跟Android中的RecyclerView等價了,因為它會自動回收列表資料:

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Sample App"),
        ),
        body: ListView.builder(
            itemCount: widgets.length,
            itemBuilder: (BuildContext context, int position) {
              return getRow(position);
            }));
  }

  Widget getRow(int i) {
    return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}
複製程式碼

與建立ListView不同的是,建立ListView.Build需要傳入兩個主要引數,一個是初始列表的長度,一個是itemBuild函式(構建列表條目的函式)。

itemBuildAndroid中的adapter#getView()方法類似,它給你一個位置,然後需要返回你需要在對應位置上渲染的行。

最後注意到, onTab函式已經不需要在創新建立資料列表了,取而代之的是通過.add()來新增到其中。

九、文書處理

1. 如何給文字控制元件設定字型?

Android SDK(Android O),可以建立一個Font資源,然後作為FontFamily引數傳入TextView。而在Flutter中,則是將字型檔案放到一個資料夾中,然後在pubspec.yaml中引用即可,跟引入圖片是類似的。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic
複製程式碼

然後在Text控制元件中使用:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Sample App"),
    ),
    body: Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
複製程式碼

2. 如何給Text控制元件定義風格? 除了字型,我們還可以定義Text控制元件的其他風格屬性,Text控制元件的風格引數中包含一個TextStyle物件,我們可以定義它的很多引數,例如:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

十、表單輸入(Form input)

有關表單的更多資訊請檢視Flutter cookbook 中的這裡Retrieve the value of a text field

1. 對應於輸入框中的“hint”的是啥?Flutter中,可以通過給Text控制元件傳入一個InputDecoration物件來顯示“hint”或者佔位字元,如下示例:

body: Center(
  child: TextField(
    decoration: InputDecoration(hintText: "This is a hint"),
  )
)
複製程式碼

2. 如何展示驗證錯誤資訊?

和顯示hint一樣,傳一個InputDecoration物件給Text控制元件的建構函式即可。

但是你肯定不想一開始就顯示錯誤,而是在出現錯誤時才顯示,因此發生錯誤時傳入一個新的InputDecoration物件即可。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(
        child: TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = RegExp(emailRegexp);

    return regExp.hasMatch(em);
  }
}
複製程式碼

十一、Flutter Plugins

1. 如何訪問GPS感測器?

使用geolocator社群外掛

2. 如何訪問相機? 使用這個image_picker

3. 如何登入Facebook?

使用 flutter_facebook_login

4. 如何使用Firebase ? 大部分Firebase函式轉換自 first party plugins,以下是第一批整合的外掛,由Flutter團隊維護:

4. 如何構建自定義的 Native integration(本地整合)

如果沒有找到我們想要的外掛,那麼可以檢視the developing packages and plugins,學習如何構建我們自己的外掛。

Flutter的外掛架構,類似於EventBus:發出訊息給接收器處理,接收器處理後將結果返回過來。在這裡,接收器程式碼執行在本地層,也就是AndroidIOS.

5. 如何在Flutter應用中使用NDK?

如果你已經在Android應用中使用了NDK,並且想要讓Flutter也能使用到這些類庫,那麼就需要構建自定義外掛了。

首先讓自定義的外掛能與Android應用互動,即在Android應用中通過JNI呼叫native函式,一旦拿到拿到結果了,就回送給Flutter,然後渲染結果。

目前不支援直接從Flutter中呼叫native程式碼。

十二、 主題(themes)

1. 如何給應用定製主題?

Flutter自帶Material Design風格的主題,不像Android可以在XML檔案中宣告主題,然後在AndroidManifest.xml中使用。在Flutter中是在頂層控制元件中宣告主題的。

如果想要在應用中充分使用Material元件,那麼可以使用MaterialApp作為應用的入口。MaterialApp包含有大量的實現了Material Design風格的通用控制元件,它建立在WidgetsApp之上,只是新增了具有Material特性的功能。

同樣也可以使用WidgetsApp作為應用控制元件,它提供了一些相同的功能,但不如MaterialApp豐富。

想要在任意子元件上自定義顏色和風格的話,那麼給MaterialApp傳入一個ThemeData物件,例如,在下面示例中,初始樣本顯示為藍色,而文字選擇後顯示為紅色。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: SampleAppPage(),
    );
  }
}
複製程式碼

十三、資料庫與本地儲存

1. 怎樣訪問Shared Preference?

Flutter中,可以通過Shared_Preferences plugin來實現這些功能,這個外掛包含了Shared PreferencesNSUserDefaultsios平臺)

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: RaisedButton(
            onPressed: _incrementCounter,
            child: Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  prefs.setInt('counter', counter);
}
複製程式碼

2. 如何訪問SQLite資料庫?

使用SQFlite外掛

十五、通知

1. 如何設定推送通知?

Android中可以使用Firebase Cloud Messaging給應用設定推送通知。

Flutter中,通過Firebase_Messaging可以使用到這一功能,更多關於使用Firebase Cloud Messaging API的資訊,請檢視firebase_messaging外掛文件。

相關文章