Flutter 開發小結 | Tips

接觸 Flutter 已經有一陣子了,期間記錄了不少開發小問題,苦於忙碌沒時間整理,最近項目進度步上正軌,藉此機會抽出點時間來統一記錄這些問題,並分享項目開發中的一點心得以及多平臺打包的一些注意事項,但願能對你們有所幫助😁。php

UI 組件使用

官方爲咱們提供了大量原生效果的組件,如以 Android 中常見的 Material Design 系列組件和 iOS 系統中讓設計師們「欲罷不能」的 Cupertino 系列組件。從我這一個月左右對於 Flutter UI 組件的使用狀況來看,不得不感慨一句:「真香」。因爲本人以前是作 Android 開發的,因此對於 Android 方面的一些「詬病」深有體會。例如,設計師常常讓咱們還原設計稿中的陰影效果,通常須要設置陰影顏色、x/y偏移量和模糊度等,然而 Android 原生並無提供支持全部這些屬性的一款組件,因此只能咱們本身經過自定義控件去實現,如今還有多少人依然經過 CardView 來「魚目混珠」呢?然而,在 Flutter 中就無需擔憂這種問題,經過相似前端中經常使用的盒子組件—— Container 就能夠輕鬆實現。前端

固然,Flutter 雖然很強大,但 UI 組件也不是萬能的,跨平臺之路註定漫長而佈滿荊棘,偶爾也會伴隨着一些小問題。android

TextField

  • 軟鍵盤彈起後組件溢出的問題git

    因爲頁面不支持滾動,一旦使用 TextField,軟鍵盤彈起後很容易會覆蓋一些UI組件,若是不覺得意,那麼下面這個問題就會成爲「屢見不鮮」:github

    A RenderFlex overflowed by xx pixels on the bottom.
    複製代碼

    經常使用的解決方案就是經過嵌套一層 SingleChildScrollView 來規避,當軟鍵盤彈起時,下方的組件會被軟鍵盤自動頂上去。json

  • HintText 不居中問題canvas

    這個問題不少人應該都遇到過,當咱們在項目中設置中文 Locale 後,在 TextField 的 InputDecoration 中設置 hintText 時,會發現提示文本向下偏移幾個像素,這應該屬於 Flutter 的bug。如何解決這個問題呢?很簡單,只須要設置 textBaseine 屬性,以下代碼所示:swift

    TextFormField(
         decoration: InputDecoration(
         		prefixIcon: Icon(
                Icons.lock_outline
            ),
            hintText: S.of(context).loginPasswordHint,
         ),
         style: TextStyle(
           	/// handle hint text offset problem.
            textBaseline: TextBaseline.alphabetic
         ),
         keyboardType: TextInputType.number,
         onSaved: (password) {},
    )
    複製代碼

    具體可參考:github.com/flutter/flu…安全

  • 焦點問題bash

    輸入框的焦點問題主要體如今兩點:

    1. 前往另外一個頁面返回後自動彈出了軟鍵盤(即自動獲取了焦點)
    2. iOS手機上切換至數字鍵盤後沒法關閉軟鍵盤

    這兩個問題其實均可以藉助 FocusNode 來解決,先來看下面一段代碼:

    FocusNode _writingFocusNode = FocusNode();
    ...
      
      void _clearTextFieldFocus() {
        if (_writingFocusNode.hasFocus) {
          _writingFocusNode.unfocus();
        }
      }  
    複製代碼

    上述代碼建立了一個 FocusNode 對象,並聲明瞭移除焦點的方法,相信你們不難判斷出。此外,咱們須要給 TextFieldfocusNode 屬性傳入咱們建立的 _writingFocusNode。問題一中,咱們能夠在頁面跳轉前先移除焦點,這樣,從二級頁面返回後輸入框就不會自動彈出軟鍵盤。問題二中,咱們能夠在用戶點擊空白區域後自動移除焦點(關閉軟鍵盤),如下代碼供參考:

    Widget _buildInputArea() =>
          Stack(
            children: <Widget>[
              // 經過空白區域的點擊事件來關閉軟鍵盤
              GestureDetector(
                onTap: () {
                  _clearTextFieldFocus();
                },
                child: Container(
                  /// 此處注意設置背景顏色,不然默認透明色可能會穿透,沒法響應點擊事件
                  color: AppTheme.surfaceColor,
                  width: MediaQuery.of(context).size.width,
                  height: MediaQuery.of(context).size.height,
                ),
              ),
              Column(
                children: <Widget>[
                  ScreenUtils.verticalSpace(32),
                  // account input edit text
                  Padding(
                    padding: EdgeInsets.only(bottom: AutoSize.covert.dpToDp(12)),
                    child: TextField(
                      controller: _accountTextController,
                      decoration: InputDecoration(
                        prefixIcon: Padding(
                          padding: EdgeInsets.all(AutoSize.covert.dpToDp(12)),
                          child: ImageIcon(AssetImage(ImageAssets.ic_login_user_input)),
                        ),
                        hintText: S.of(context).loginAccountHint,
    
                      ),
                      keyboardType: TextInputType.number,
                    ),
                  ),
    
                  // password input edit text
                  Padding(
                    padding: EdgeInsets.only(bottom: AutoSize.covert.dpToDp(12)),
                    child: ValueListenableBuilder(
                      valueListenable: obscureTextModel,
                      builder: (context, value, child) => TextField(
                        controller: _passwordTextController,
                        obscureText: value,
                        decoration: InputDecoration(
                          prefixIcon: Padding(
                            padding: EdgeInsets.all(AutoSize.covert.dpToDp(12)),
                            child: ImageIcon(AssetImage(ImageAssets.ic_login_pwd_input)),
                          ),
                          suffixIcon: IconButton(
                              icon: Icon(value ? Icons.visibility_off : Icons.visibility, size: AutoSize.covert.dpToDp(20)),
                              onPressed: () {
                                obscureTextModel.value = !value;
                              }
                          ),
                          hintText: S.of(context).loginPasswordHint,
                        ),
                        keyboardType: TextInputType.text,
                      ),
                    ),
                  ),
                ],
              ),
            ],
          );
    
    複製代碼

Container

  • 盒子模型特色

    對於接觸過前端的人來講,應該都領略過「盒子模型」的強大了,因此,Container 的強大之處相信也不用我多說了:它幾乎是一個萬能的「容器」,既能設置 margin、padding、aligment,又能夠裝飾它的背景 docoration 屬性,例如陰影效果、漸變色、圓角效果等等。

  • 設置背景色問題

    Container雖好,但也須要在使用時注意一些問題,例如,它的源碼註釋中就說到:咱們能夠經過 colordecoration 來設置盒子背景,但二者卻不能同時存在,若是咱們既但願保留背景色,又想使用裝飾器 (decoration),咱們能夠直接設置 BoxDecorationcolor 屬性。

SafeArea

Android中存在狀態欄、底部導航欄,而 iOS 中也存在狀態欄和"底部導航條",因此若是咱們頁面中的邊界部分須要固定顯示一些小組件,那麼咱們最好可以在最外層嵌套一層 SafeArea 組件,即讓UI組件處於「安全區域」,不至於引發適配問題。

Material( 
  color: AppTheme.surfaceColor,
  child: SafeArea(
    child: Container(),
  ),
)
複製代碼

列表組件

Flutter中常見的列表組件有 ListView、GridView、PageView 等,一個完整的應用確定也離不開這些組件。咱們在使用時,須要留意如下幾點:

  • Vertical viewport was given unbounded height 問題

    做爲初學者,咱們在初期應該都碰到過這個問題:Vertical/Horizontal viewport was given unbounded height,這是因爲咱們沒有給列表組件指定高度或者寬度而引發的,通常出現場景是在 Column 中,咱們能夠給列表組件包裹一層盒子或者被 Expanded 包裹,讓其儘量佔據最大空間:

    Column(
    	children:[
    		...,
    		Expanded(
                      child: GridView.builder(
                  ....
                      )           
        	        )
           )
      ]
    )
    複製代碼
  • physics 屬性

    作過原生開發的都知道,在 Android 中,支持滾動的組件滑到頂或者滑到底後自帶 colorPrimary 色調的水波紋效果,至於iOS,則是越界回彈效果。你拿着 Android 中的默認效果去給設計師看,「親果黨」的他們確定不幹了,硬是讓你改爲 iOS 的回彈效果。所幸,Flutter 早就考慮到了這一點,支持滑動的組件中都提供了 physics 屬性,只須要將其設置爲 BouncingScrollPhysics 就能完美實現回彈效果。同時,physics 還有其餘屬性,這裏再也不一一介紹,你們能夠去查看相關文檔和源碼,這裏提一下 NeverScrollableScrollPhysics,即禁止滑動。這個有什麼用呢?其實仍是挺有用的,好比嵌套的兩個滑動組件中就能夠將其中一個的 physics 屬性設置爲 NeverScrollableScrollPhysics,這樣能夠簡單快速解決滑動衝突。此外,有些特殊場景咱們可能不但願用戶能夠滑動,而是經過按鈕點擊來控制列表滑動,這時候,設置該屬性就再好不過啦。

自定義彈窗

Flutter 爲咱們提供了一些內置的定製彈窗,這裏再也不一一說明了。如何自定義彈窗?其實很簡單,只須要明白:彈窗即頁面。如下面的效果爲例:

自定義彈窗效果圖

相信對於你們來講,上面的UI頁面實現起來並不困難,那咱們離 Dialog 效果僅剩一步之遙了:點擊空白區域關閉。其實,在上面的某段代碼中我已經貼了關鍵代碼,細心的小夥伴應該也察覺到了,沒錯,咱們能夠經過 Stack 組件包裹半透明蒙層(如Container)和分享功能組件,咱們只需爲半透明蒙層增長點擊事件便可:

Stack(
        children: <Widget>[
          // 經過空白區域的點擊事件來關閉彈窗
          GestureDetector(
            onTap: () {
            	//關閉彈窗
              Navigator.maybePop(context);
            },
            child: Container(
              color: AppTheme.dialogBackgroundColor,
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
            ),
          ),
          Container(
          	child: ...
          )
     )
複製代碼

哈哈,是否是有種恍然大悟的感受,如此一來,彈窗對於咱們來講不就是寫一個頁面那麼簡單了嗎😄。

InkWell

InkWell 在 Android 中比較常見,俗稱「水波紋」效果,屬於按鈕的一種,它支持設置波紋顏色、圓角等屬性。咱們偶爾可能會遇到水波紋失效的問題,這通常是由於咱們在 InkWell 內部的 child 中設置了背景,從而致使水波紋效果被遮蓋。如何解決這個問題?其實很簡單,只須要在 InkWell 外層套上 Material 並設置 color 便可:

Material(
  color: Colors.white,
  child: InkWell(
    borderRadius: AppTheme.buttonRadius, // 圓角
    splashColor: AppTheme.splashColor,   // 波紋顏色
    highlightColor: Colors.transparent,  // 點擊狀態
    onTap: () {},												 // 點擊事件
    child: Container(
      ...
    ),
  ),
)
複製代碼

或者,咱們也能夠藉助於以前實現自定義 Dialog 的思路,使用 Stack 包裹須要點擊的區域,並將 InkWell 放在上層:

Stack(
            children: <Widget>[
              Image(),
              Material(
                  color: Colors.transparent,
                  child: InkWell(
                    splashColor: AppTheme.splashColor,
                    onTap: () {},
                  ),
                )
              )
            ],
          )

複製代碼

Theme 相關

Android中的 Theme 相信你們也不陌生了,它可以定製化咱們某類組件的風格 ,可以大幅度減小重複代碼和工做量。Flutter 中也提供了 Theme 來讓咱們配置和複用全局組件的樣式。正常來講,咱們應用的高保真設計圖中的一類組件通常風格類似,如:

高保真Theme示例

上圖兩個頁面的按鈕樣式應該是同樣的,因此咱們能夠抽離成 Theme,其餘組件同理。可作以下配置(僅供參考):

class AppTheme {
  AppTheme._();

  /// common colors used in theme, text, background or borders
  static const Color primaryColor = Color(0xFF92C9AA);
  static const Color secondaryColor = Color(0x96A3E4C5);
  static const Color colorAccent = Color(0xFFE5F378);

  static const Color textPrimaryColor = Color(0xFF2A2A2A);
  static const Color textSecondaryColor = Color(0xFF383838);
  static const Color textHintColor = Color(0xFFB3B3B3);
  static const Color borderColor = Color(0xFFE8E8E8);
  static const Color surfaceColor = Color(0xFFF9F9F9);
  static const double underlineBorderWidth = 0.6;

  static TextTheme textTheme = TextTheme(
    headline: headline,
    title: title,
    body1: body1,
    body2: body2,
    caption: small
  );

  // large title
  static const headline = TextStyle(
    fontWeight: FontWeight.bold,
    fontSize: 30,
    letterSpacing: 0.27,
    color: textPrimaryColor
  );

  // normal title
  static const title = TextStyle(
    fontSize: 18,
    letterSpacing: 0.18,
    color: textPrimaryColor
  );

  // normal text body1
  static TextStyle body1 = TextStyle(
    fontSize: 16,
    letterSpacing: 0.48,
    color: textPrimaryColor
  );

  // normal text body2
  static TextStyle body2 = TextStyle(
    fontSize: 14,
    letterSpacing: 0.25,
    color: textSecondaryColor
  );

  static TextStyle small = TextStyle(
    fontSize: 12,
    color: textHintColor
  );

  // input field border decoration style
  static const inputDecorationTheme = InputDecorationTheme(
    hintStyle: TextStyle(fontSize: 14),
    focusedBorder: UnderlineInputBorder(
      borderSide: BorderSide(width: underlineBorderWidth, color: primaryColor)
    ),
    border: UnderlineInputBorder(
      borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
    ),
    enabledBorder: UnderlineInputBorder(
        borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
    ),
    disabledBorder: UnderlineInputBorder(
        borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
    ),
  );
}
複製代碼

那麼,咱們能夠在入口程序將這套 Theme 規則應用到全局:

MaterialApp(
          title: 'xxx',
          // 全局主題配置
          theme: ThemeData(
            textTheme: AppTheme.textTheme,
            primaryColor: AppTheme.primaryColor,
            canvasColor: AppTheme.surfaceColor,
            scaffoldBackgroundColor: AppTheme.surfaceColor,
            inputDecorationTheme: AppTheme.inputDecorationTheme,
            appBarTheme: AppTheme.appBarTheme,
            //...
          ),
          home: xxxPage()
        )
複製代碼

以上僅列舉了部分常見UI組件的使用技巧和問題,若有其餘問題歡迎留言探討。

功能需求實現

除了 Flutter 中的一些 UI 組件的的使用之外,應用天然還須要涉及到不少具體的業務功能需求,常見的有第三方登陸、分享、地圖、Lottie 動畫接入、第三方字體下載和加載等等。這個時候就須要咱們靈活變通了,在保證項目進度順利進行的前提下有選擇性地去借助一些插件和工具,或者前往 Flutter 的 Github Issue 社區去尋找答案了,這裏也選擇幾個經常使用需求簡單說一下。

當前設備的系統語言

不少時候咱們須要根據當前系統使用的語言去動態選擇加載的內容,舉個例子,咱們常常須要根據當前語言去加載中文或者英文版的用戶隱私條款,咱們能夠藉助 Localizations 去獲取當前使用語言的 languageCode,進而比對和處理:

/// 判斷當前語言類型
 _navigateToUrl(Localizations.localeOf(context).languageCode == 'zh'
                      ? Api.PRIVACY_POLICY_ZH_CN
                      : Api.PRIVACY_POLICY_EN);
複製代碼

第三方登陸/分享

這部分當初考慮本身寫插件來對接原生的分享sdk,但考慮到時間成本就暫時擱置了,找到幾個不錯的插件來實現了該部分功能:

  • fluwx

    該插件應該是在微信社會化分享、支付等方面功能集成度比較高的插件了,是由 OpenFlutter 社區負責維護的,目前沒發現有什麼問題。具體配置的細節就再也不說明了,它的文檔很詳細,具體可參考:github.com/OpenFlutter…

    另外,他們組織中還有其餘不少優秀的 Flutter 相關項目,你們也能夠去學習一下。

  • flutter fake toolkit

    這個是一系列插件,包括了微信、微博、QQ、支付寶等衆多平臺,讓人佩服做者的產出率。目前使用起來也沒發現什麼大問題,也但願做者可以多邀請幾個小夥伴來維護,提高更新的頻率。這裏附上其中幾個經常使用的插件:

    QQ插件:github.com/v7lin/fake_…

    微博插件:github.com/v7lin/fake_…

    支付寶:github.com/v7lin/fake_…

Lottie動畫

相信你們對 Airbnb 公司推出的這個動畫工具已經有所耳聞了,Lottie 支持多平臺,使用同一個JSON 動畫文件,可在不一樣平臺實現相同的動畫效果。如今複雜動畫不少時候都藉助於它,可以有效減小開發成本和保持動畫的高還原度。一樣,Flutter 中也有一些封裝了 Lottie 動畫的插件,讓咱們能夠在 Flutter 上也能夠感覺到它的魅力。

這裏,我我的使用的插件是 flutter_lottie 插件,還算穩定,支持動畫屬性和進度操做,惟一遺憾就是有段時間沒更新了😂,後續考慮到 iOS 方面的兼容性可能會本身寫一個插件。在 pubspec.yaml 中依賴操做以下:

# Use Lottie animation in Flutter.
  # @link: https://pub.dev/packages/flutter_lottie
 flutter_lottie: 0.2.0
複製代碼

具體使用技巧可參考它的example:github.com/CameronStua…

這裏附上控制動畫進度的部分代碼:

int _currentIndex = 0;
  LottieController _lottieController;
  PageController _pageController = PageController();
  // the key frames of animation
  final ANIMATION_PROGRESS = [
    0.0,
    0.2083,
    0.594,
    0.8333,
    1
  ];
  // the duration of each animation sections
  final ANIMATION_TIMES = [
    2300,
    4500,
    3500
  ];
  // animation progress controller
  Animation<double> animation;
  AnimationController _animationController;

  
  @override
  void initState() {
    super.initState();
    _animationController = new AnimationController(
        duration: Duration(milliseconds: ANIMATION_TIMES[_currentIndex]), vsync: this);
    final Animation curve =
        new CurvedAnimation(parent: _animationController, curve: Curves.linear);
    animation = new Tween(begin: 0.0, end: 1.0).animate(curve);
    animation.addListener(() {
      _applyAnimation(animation.value);
    });
  }

// 佈局代碼
.......
  
  Positioned(
              bottom: 0,
              child: Container(
                width: MediaQuery.of(context).size.width,
                // 此處爲了將動畫組件居下放置
                height: AutoSize.covert.dpToDp(667),
                child: LottieView.fromFile(
                  filePath: 'assets/anims/user_guide_anim.json',
                  autoPlay: false,
                  loop: true,
                  reverse: true,
                  onViewCreated: (controller) {
                    _lottieController = controller;
                    Future.delayed(Duration(milliseconds: 1), () {
                      _animationController.forward();
                    });
                  },
                ),
              ),
            ),

// description page view
            Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              margin: EdgeInsets.only(bottom: 60),
              child: PageView(
                physics: BouncingScrollPhysics(),
                controller: _pageController,
                onPageChanged: (index) {
                  setState(() {
                    _currentIndex = index;
                    _animationController.duration = Duration(milliseconds: ANIMATION_TIMES[index]);
                  });
                  Future.delayed(Duration(microseconds: 600), () {
                    _animationController.forward(from: 0);
                  });
                },
                children: _buildPageGroup(),
              ),
            ),

......
  
  void _applyAnimation(double value) {
    var startProgress = ANIMATION_PROGRESS[_currentIndex];
    var endProgress = ANIMATION_PROGRESS[_currentIndex + 1];
    var progress = startProgress + (endProgress - startProgress) * value;
    _lottieController.setAnimationProgress(progress);
  }

複製代碼

簡單解釋一下上述代碼邏輯,咱們這裏主要藉助於 Lottie 來實現用戶引導頁的切換動畫,引導頁分爲三個畫面,因此須要咱們記錄和保存動畫的關鍵幀和每段畫面的執行時間。至於動畫的控制執行權交由上層的 PageView 來滑動實現,每次滑動經過 AnimationControllersetState((){}) 來控制和刷新每段動畫的執行時間和執行刻度。具體demo效果以下所示:

Flutter中的lottie動畫效果

外部字體下載和加載

若是接觸過文字編輯功能開發的小夥伴應該都知道,咱們通常會提供幾十種字體供用戶使用,固然,咱們不可能在項目打包時就放入這麼多字體包,這樣顯而會嚴重增長安裝包大小。咱們通常的作法是:當用戶第一次點擊想使用某個字體時,咱們會先將其下載到手機本地存儲,而後加載字體,後續當用戶再次選擇該字體,那麼直接從本地加載便可。那麼問題來了,Flutter 目前的示例中僅爲咱們提供了從本地 Asset 目錄下加載字體的方式,顯然想要實現上述需求,須要咱們本身尋求出路。

幸運的是,上帝爲咱們關上了一扇門,也爲咱們打開了一扇窗,Flutter 中爲咱們提供了一個 FontLoader 工具,它有一個 addFont 方法,支持將 ByteData 格式數據轉化爲字體包並加載到應用字體資源庫:

/// Registers a font asset to be loaded by this font loader.
  ///
  /// The [bytes] argument specifies the actual font asset bytes. Currently,
  /// only TrueType (TTF) fonts are supported.
  void addFont(Future<ByteData> bytes) {
    if (_loaded)
      throw StateError('FontLoader is already loaded');

    _fontFutures.add(bytes.then(
        (ByteData data) => Uint8List.view(data.buffer, data.offsetInBytes, data.lengthInBytes)
    ));
  }
...

 /// Loads this font loader's font [family] and all of its associated assets
  /// into the Flutter engine, making the font available to the current
  /// application.
  ///
  /// This method should only be called once per font loader. Attempts to
  /// load fonts from the same loader more than once will cause a [StateError]
  /// to be thrown.
  ///
  /// The returned future will complete with an error if any of the font asset
  /// futures yield an error.
  Future<void> load() async {
    if (_loaded)
      throw StateError('FontLoader is already loaded');
    _loaded = true;

    final Iterable<Future<void>> loadFutures = _fontFutures.map(
        (Future<Uint8List> f) => f.then<void>(
            (Uint8List list) => loadFont(list, family)
        )
    );
    return Future.wait(loadFutures.toList());
  }
複製代碼

如此一來,那咱們解決思路也就「手到擒來」了:只須要將字體下載到本地並以文件形式存儲,在使用時將字體文件再轉爲 ByteData 數據格式供 FontLoader 加載便可。這裏附上簡化後的部分關鍵代碼:

/// 加載外部的字體
Future loadFontFile(LetterFont font) async {
    // load font file
    var fontLoader = FontLoader(font.fontName);
    fontLoader.addFont(await fetchFont(font));
    await fontLoader.load();
  }
/// 從網絡下載字體資源
Future<ByteData> fetchFont(LetterFont font) async {
    final response = await https.get(
        font.fontUrl);

    if (response.statusCode == 200) {
      // 這裏也能夠作保存到本地的邏輯處理
      return ByteData.view(response.bodyBytes.buffer);
    } else {
      // If that call was not successful, throw an error.
      throw Exception('Failed to load font');
    }
  }
複製代碼

打包上架相關

打包方面也有一部分細節須要注意一下,這裏談一下 Android 和 iOS 開發環境配置和打包差別以及列舉部分常見問題,其餘問題因人而異,也因版本而異,就不單獨拿出來說了。

Android方面

  1. 開發工具

    Android studio3.6穩定版

  2. 代碼編譯環境

    Kotlin + AndroidX

    目前Flutter建立項目默認勾選兩個選項

  3. 版本號配置

    android/app/build.gradle 中配置 flutterVersionCodeflutterVersionName

    注意:若是在 pubspec.yaml 中配置了version,那麼 Flutter 具體打包的版本會實際根據 pubspec.yamlversion 來構建。

  4. 網絡配置

    目前 Android 官方不建議採用http請求格式,推薦使用 https,因此,若是項目中使用到了http格式請求,那麼須要添加網絡配置。首先在 android/app/src/main/res 路徑下建立名爲 xml 的文件夾:而後建立名爲 network_security_config 的 xml 文件,接着將以下代碼複製進去:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true"/>
    </network-security-config>
    複製代碼

    而後在 AndroidManifest.xml 文件中設置 networkSecurityConfig 屬性便可:

    <application android:name="io.flutter.app.FlutterApplication" android:label="Timeory" android:icon="@mipmap/ic_launcher" tools:replace="android:name" android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/network_security_config" tools:ignore="GoogleAppIndexingWarning">
       ......
    </application>
    複製代碼
  5. 權限配置

    通常咱們項目中都會用到權限申請,而且不少 flutter 插件中也會要求咱們去本身配置權限,咱們可能須要在 AndroidManifest.xml 文件中添加以下經常使用權限(只是樣例):

    <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
        <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    複製代碼

    固然這些仍是不夠的,Android6.0及以上,咱們還須要在代碼中動態申請權限,Flutter中有不少優秀的權限申請插件,iOS 上面通常沒問題,Android因爲碎片化比較嚴重,可能會在不一樣機型上出現各類奇怪問題,好比,紅米部分機型藉助於 permission_hanlder 插件申請定位權限可能會失敗的問題,這裏須要注意一下,留個心眼。

  6. Logo 配置

    Logo 須要在 android/app/src/main/res 中添加和配置,通常只須要準備 hdpimdpixhdpixxhdpixxxhdpi格式便可。另外,Android8.0 及以上須要適配圓角logo,不然在部分高版本機型上會顯示 Android 默認的機器人logo。

    具體能夠參考該文章:blog.csdn.net/guolin_blog…

  7. 打包

    通常狀況下咱們經過 flutter build apk 來打包,生成的安裝在 build/app/outputs/apk/release 目錄下,這樣打出來的包通常比較大,由於它包含了 arm64-v8aarmeabi-v7ax86_64 三種cpu架構的包。你們能夠根據須要有選擇性的針對特定機型的cpu架構打包,執行以下命令便可:

    flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi
    複製代碼

    執行完畢後就會在 release 目錄下生成三種格式的apk包。

    另外,你們能夠選擇一些apk體積優化的方案,具體可參考:

    my.oschina.net/u/1464083/b…

    www.jianshu.com/p/555c948e5…

iOS 方面

因爲本人以前作 Android 開發,沒有接觸過 iOS,因此打包到 iOS 平臺仍是遇到很多問題。

  1. 開發工具:

    Xcode11.3.1 穩定版 (打包環境) + Visual Studio Code 1.42.1 (編碼環境)

  2. 代碼編譯環境:Swift + Objective-C (目前建立Flutter項目默認勾選爲swift,因爲項目啓動時Flutter還沒有更新該配置,因此項目中部分插件採用的是oc),但願後面逐步替換爲主流的swift。

  3. 版本號配置:

    只須要在Xcode中 Runner -> targets -> General -> Identity 配置便可。

  4. 網絡配置

    iOS 中,官方一樣約束咱們使用 https 請求,若是咱們須要暫時使用http格式請求來測試,能夠作以下配置:

    Runner -> targets -> General -> Info 中添加 App Transport Security Settings 屬性,並在此屬性標籤內添加 Allow Arbitrary Loads 子屬性,並將值設置爲 YES 便可。

  5. Logo配置

    iOS 中的 logo 配置只須要找到以下入口:

    點擊 ➡️ 便可進入 logo 資源目錄,默認的爲 Flutter 的官方 logo,咱們只須要根據具體 logo 尺寸去替換資源便可。

  6. 國際化語言配置

    項目中若是支持國際化語言,Android 中無需額外配置,iOS 中須要在 Info.plist 中添加 Localized resource can be mixed 屬性,並設置值爲 YES 便可,不然APP運行後可能會出現實際展現的是英文的狀況。

  7. 打包相關

    Xcode打包時切記要使用穩定版,不要使用 beta 版本,不然可能會出現下面的問題:

以上就是本人對近期 Flutter 開發過程的一點簡單總結,若是可以幫助到您那將再好不過😄。剛接觸 Flutter 不久,相關闡述可能不夠嚴謹或存在理解錯誤,若是您發現了,還請指出,感謝您的閱讀。

相關文章
相關標籤/搜索