Flutter開發中的一些Tips

唯鹿發表於2019-06-12

學習Flutter也有一陣子了。閒著沒事,用了公司一個已經涼涼的App設計圖來練手。當然了介面不可能用的了,所以都是些死資料,實現效果可以說是很完美了(得到了設計的認可。。。)。當然自己也是邊查邊寫,也借鑑了許多Github上優秀的Flutter專案。現在開源出來(附帶設計圖),供大家交流學習。希望多多Star、Fork支援,有問題可以Issue。附上鍊接:github.com/simplezhli/…

在這裡插入圖片描述

本篇主要分享一下自己在此專案中遇到的問題及心得,希望對你有所幫助!

1.部件溢位

異常大致如下:

A RenderFlex overflowed by 22 pixels on the bottom.
複製程式碼

導致的原因就是在水平或者垂直方向上的內容超過了父部件的大小。一般來說我們的頁面不存在這樣的問題,因為根據頁面的設計,事先可以預料到是否超出。不過要注意到有輸入法彈出的頁面。比如我下面的這個例子:

Flutter開發中的一些Tips Flutter開發中的一些Tips

可以看到底部溢位了22個畫素,可能在18:9的手機以上不太會出現這種問題,因為螢幕的高度足夠。但是這種16:9的手機可能會暴露出來。解決的方法有兩種:

  1. 包一層SingleChildScrollView,讓你的頁面可以滑動起來。

  2. Scaffold中設定resizeToAvoidBottomInset為false。預設為ture,防止部件被遮擋。如果使用了這個方法,如果底部有輸入框,則會造成遮擋。

大家可以根據實際需求選擇。

2.輸入框的遮擋

頁面如下:

Flutter開發中的一些Tips

底部有輸入框,同時“提交”的按鈕固定在底部。一開始覺得既然固定在底部,那就使用Stack配合Positioned來實現,然而就導致輸入法彈出時,發生遮擋。

Flutter開發中的一些Tips

上圖中,我選中了最後一個輸入框,但因為輸入法預設都是在輸入框的下方彈出,然而上面蓋著這個“提交”按鈕,發生了遮擋。

最終我的解決方法就是使用Column配合Expanded來實現。修復後如下:

Flutter開發中的一些Tips

3.SafeArea

一旦有部件固定在頂部或者底部(嚴謹點的話可以說是在螢幕的四邊)。那我我們最好使用SafeArea來包一下。因為Android 和 IOS都有狀態列,甚至IOS還有叫做“HomeIndicator”的橫條。所以一不留神就會出現適配問題。

我們在Flutter中常使用的BottomNavigationBarAppBar 其實就在內部處理了此類問題。以 AppBar原始碼為例:

class _AppBarState extends State<AppBar> {

  @override
  Widget build(BuildContext context) {
    
    if (widget.primary) {
      appBar = SafeArea(  // <--- 1
        top: true,
        child: appBar,
      );
    }

    return Semantics(
      container: true,
      child: AnnotatedRegion<SystemUiOverlayStyle>(
        value: overlayStyle,
        child: Material( // <--- 2
          color: widget.backgroundColor
            ?? appBarTheme.color
            ?? themeData.primaryColor,
          child: Semantics(
            explicitChildNodes: true,
            child: appBar,
          ),
        ),
      ),
    );
  }
}
複製程式碼

所以使用方法為:

Material( // 需要顏色填充到邊界區域可以使用
  color: Colors.white,
  child: SafeArea(
    child: Container(),
  ),
)		
複製程式碼

還是上面的頁面,我們對比一下處理前後的效果:

Flutter開發中的一些Tips Flutter開發中的一些Tips

4.善用Theme

Flutter 在開發中,讓人詬病的就是大量的巢狀,而我們只能儘量避免。比如將一些部件、屬性進行封裝,避免重複的書寫。不過封裝也講究使用場景。如果這種樣式的部件僅僅只是某一兩處使用,封裝顯得有點小題大做。並且封裝的大而全也會增加使用的複雜度。那麼這時就可以使用Theme這種辦法。

舉一個例子,在下圖中圈起來的部分有三個按鈕,它們的高度相同,文字、圓角大小也相同。如果每一個都去設定這些屬性,未免太過麻煩。

Flutter開發中的一些Tips

這時我們使用Theme去統一修改它們的樣式,就會很方便了。

			Theme( 
              data: Theme.of(context).copyWith(
                buttonTheme: ButtonThemeData(
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
                  minWidth: 64.0,
                  height: 30.0,
                  materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                  shape:RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(4.0),
                  )
                ),
                textTheme: TextTheme(
                  button: TextStyle(
                    fontSize: 14.0,
                  )
                )
              ),
              child: Row(
                children: <Widget>[
                  FlatButton(
                    color: Color(0xFFF6F6F6),
                    onPressed: (){},
                    child: Text("聯絡客戶"),
                  ),
                  ......
                  FlatButton(
                    color: Color(0xFFF6F6F6),
                    onPressed: (){},
                    child: Text("拒單"),
                  )
                ],
              ),
            )			
複製程式碼

同時使用Theme還可以修改許多預設的設定,比如FlatButton的預設寬度為88,高度為36,但是FlatButton中沒有直接修改的屬性,網上好多的方法都是通過包一層Container去修改,不僅增加的巢狀,有些需求還不能達到。所以善用Theme可以讓你省時省力,不過缺點就是你需要去翻翻原始碼,尋找使用這些Theme的地方。

5.注意平臺差異

注意部分元件在Android與IOS平臺之間的差異。

  1. ScaffoldAppBarAppBar中預設的title在Android中靠左顯示,IOS中居中顯示。如果需要兩個平臺效果統一,需要設定在AppBar中主動設定centerTitle屬性。同時AppBar的返回箭頭圖示也不相同,統一的話需要自定義leading

在這裡插入圖片描述

  1. 頁面跳轉如果使用MaterialPageRoute來做過渡效果,注意Android中新的頁面會從螢幕底部滑動到螢幕頂部,IOS中新的頁面會從螢幕右側滑動到螢幕左側。

如果需要兩個平臺效果統一,我們不使用自帶效果,可以自定義一個。

Navigator.push(context, PageRouteBuilder(transitionDuration: Duration(milliseconds: 300),
  pageBuilder: (context, animation, secondaryAnimation){
    return new FadeTransition( //使用漸隱漸入過渡,
      opacity: animation,
      child: TestPage(),
    );
  })
);
複製程式碼

要麼修改Theme,統一兩平臺的實現。:

class MyApp extends StatelessWidget {

  static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
    TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
    TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
  };
  
  @override
  Widget build(BuildContext context) {
    
    return MaterialApp(
      theme: ThemeData(
        pageTransitionsTheme: PageTransitionsTheme(
          builders: _defaultBuilders
        )
      ),
      ...
    );
  }
}
複製程式碼
  1. ScrollPhysics效果,可以滑動的部件都有一個physics屬性。滑動到邊界時,Android平臺為邊緣陰影的效果ClampingScrollPhysics,IOS為回彈效果BouncingScrollPhysics。如果需要統一,可以指定physics屬性。

  2. 狀態列方面,Android平臺預設是半透明的效果,IOS則是透明效果。比如Android要實現IOS的效果,可以設定狀態列為透明。不過IOS要實現Android的效果則不行。。。,難道只能自定義?有知道方法的可以分享一下。

void main(){
  runApp(MyApp());
  // 透明狀態列
  if (Platform.isAndroid) {
    SystemUiOverlayStyle systemUiOverlayStyle = 
    SystemUiOverlayStyle(statusBarColor: Colors.transparent);
    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
  }
}
複製程式碼
  1. 輸入鍵盤

TextFieldkeyboardType屬性設定為TextInputType.phoneTextInputType.number時,IOS系統彈出的數字輸入鍵盤沒有"完成"按鈕,導致輸入法無法關閉。當然了Android不存在這個問題。

比較成熟有效的方案是在鍵盤彈出的上方懸浮一個按鈕,點選可以關閉鍵盤。當然了,這種問題也有對應的庫可以解決,我使用的是flutter_keyboard_actions來解決了這個問題。因為在Android端我發現了部分輸入法的相容問題,所以只針對IOS做了處理。大家可以看一下前後對比圖,具體實現程式碼可以參考flutter_keyboard_actions的文件和我的專案程式碼:

Flutter開發中的一些Tips Flutter開發中的一些Tips

當然平臺差異不僅僅是這麼多,比如IOS自帶側滑返回等。具體我們可以去檢視呼叫TargetPlatform列舉類的程式碼。

如果你覺得這樣真麻煩,我給你支個大招,修改ThemeDataplatform,指定一個平臺。

class MyApp extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    
    return MaterialApp(
      theme: ThemeData(
        platform: TargetPlatform.android
      ),
      ...
    );
  }
}
複製程式碼

其次就是使用TextInputType.number在IOS中彈起的鍵盤沒有小數點符號。在輸入金額型別資料時,需要將keyboardType屬性設定為TextInputType.numberWithOptions(decimal: true)

6.keyboardType

keyboardType屬性主要含義為彈起的鍵盤型別,並不代表輸入資料的型別

而在Android開發中,在EditText中設定android:inputType不僅可以指定彈起的鍵盤型別,同時也確定了輸入資料的型別,也就是內建了資料的格式校驗。Flutter中並沒有後者,所以可能一開始你是TextInputType.number,但是在輸入法中切換成中文鍵盤,一樣可以輸入中文字元。所以資料的校驗需要我們使用inputFormatters自己處理。

比如TextInputType.phone時可以使用WhitelistingTextInputFormatter 白名單校驗,只允許輸入0~9:

	TextField(
      keyboardType: TextInputType.phone,
      inputFormatters: [WhitelistingTextInputFormatter(RegExp("[0-9]"))]
    )
複製程式碼

輸入密碼時可以使用BlacklistingTextInputFormatter 黑名單校驗,除去中文字元:

	TextField(
      keyboardType: TextInputType.text,
      inputFormatters: [BlacklistingTextInputFormatter(RegExp("[\u4e00-\u9fa5]"))]
    )
複製程式碼

輸入小數時,可以自定義TextInputFormatter來限制輸入小數格式:

	TextField(
      keyboardType: TextInputType.numberWithOptions(decimal: true),
      inputFormatters: [UsNumberTextInputFormatter()]
    )

//來源:https://www.cnblogs.com/yangyxd/p/9639588.html
class UsNumberTextInputFormatter extends TextInputFormatter {
  static const defaultDouble = 0.001;
  static double strToFloat(String str, [double defaultValue = defaultDouble]) {
    try {
      return double.parse(str);
    } catch (e) {
      return defaultValue;
    }
  }

  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    String value = newValue.text;
    int selectionIndex = newValue.selection.end;
    if (value == ".") {
      value = "0.";
      selectionIndex++;
    } else if (value != "" && value != defaultDouble.toString() && strToFloat(value, defaultDouble) == defaultDouble) {
      value = oldValue.text;
      selectionIndex = oldValue.selection.end;
    }
    return new TextEditingValue(
      text: value,
      selection: new TextSelection.collapsed(offset: selectionIndex),
    );
  }
}
複製程式碼

7.InkWell

InkWell有的叫濺墨效果,有的叫水波紋效果。使用場景是給一些無點選事件的部件新增點選事件時使用(也支援長按、雙擊等事件),同時你也可以去修改它的顏色和形狀。

InkWell(
  borderRadius: BorderRadius.circular(8.0), // 圓角
  splashColor: Colors.transparent, // 濺墨色(波紋色)
  highlightColor: Colors.transparent, // 點選時的背景色(高亮色)
  onTap: () {},// 點選事件
  child: Container(),
);
複製程式碼

不過有時你會發現並不是包一層InkWell就一定會有濺墨效果。主要原因是濺墨效果是在一個背景效果,並不是覆蓋的前景效果。所以InkWell中的child一旦有設定背景圖或背景色,那麼就會遮住這個濺墨效果。如果你需要這個濺墨效果,有兩種方式實現。

  1. 包一層 Material,將背景色設定在 Material中的color裡。
Material(
  color: Colors.white,
  child: InkWell(),
)
複製程式碼
  1. 使用Stack佈局,將InkWell放置在上層。這種適用於給圖片新增點選效果,比如Banner圖的點選。
		Stack(
            children: <Widget>[
              Positioned.fill(
                child: Image(),
              ),
              Positioned.fill(
                child: Material(
                  color: Colors.transparent,
                  child: InkWell(
                    splashColor: Color(0X40FFFFFF),
                    highlightColor: Colors.transparent,
                    onTap: () {},
                  ),
                ),
              )
            ],
          )
複製程式碼

8.保持頁面狀態

比如點選導航欄來回切換頁面,預設情況下會丟失原頁面狀態,也就是每次切換都會重新初始化頁面。這種情況解決方法就是PageViewBottomNavigationBar結合使用,同時子頁面State中繼承AutomaticKeepAliveClientMixin並重寫wantKeepAlive為true。程式碼大致如下:

class _TestState extends State<Test> with AutomaticKeepAliveClientMixin{

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}

複製程式碼

詳細的可以看這篇文章:Flutter 三種方式實現頁面切換後保持原頁面狀態

9.依賴版本問題

首先這裡建議凡是Flutter的外掛在填寫版本號時不要使用^符號。

在這裡插入圖片描述

^符號意味著你可以使用此外掛的最新版本(大於等於當前版本)。這會導致什麼問題呢?可能你前一天程式碼還能跑起來,今天就編譯出錯了。因為這些外掛中包括Android、IOS的所用依賴環境配置,常見的就是新版本使用了AndroidX的依賴,但是還有些外掛並沒有使用AndroidX,導致了兩者的衝突。

我之前在看flutter-go的程式碼時,就是因為webview的外掛突然升級了,導致了安裝失敗。具體問題可以看這裡。所以在程式碼穩定的情況下不建議使用^符號。

發生了這種問題,有以下幾個解決方法:

  1. 使用非AndroidX的版本外掛。(優點就是見效快。缺點就是此外掛後續的更新無法使用)

  2. 手動修改外掛的衝突,因為Flutter外掛的程式碼是可以直接修改的,所以你可以手動修改掉這些衝突,統一外掛的版本(優點就是可以使用最新的版本。缺點就是這種方法首先麻煩,其次不利於團隊開發使用)

我偏好使用第二種,只要做好修改的相關記錄就行,算是一勞永逸。

10.Flutter Android 打包

打包本身流程沒有問題,配置好籤名檔案,執行flutter build apk命令。但是發現打包後沒有將外掛中的AndroidManifest.xml檔案合併。比如我有使用image_picker外掛,它的AndroidManifest.xml檔案如下:

在這裡插入圖片描述

可以看到有許可權的及Android 7.0FileProvider的宣告。諸如此類的資訊沒有打包進去(但是引用xml中的flutter_image_picker_file_paths檔案卻在),導致我實際使用這些功能時沒有反應,但是在平時的除錯過程中卻是好的。

中間我發現打包後的App名稱也是之前的,懷疑是快取問題,所以我手動刪除了專案根目錄的build.gradle資料夾,重新打包就好了。所以打包後最好檢查一下AndroidManifest.xml檔案,避免此類快取造成的問題。

11.其他

  1. Container 功能強大,設定寬高、padding、margin、背景色、背景圖、圓角、陰影等都可以使用它。

  2. 有些widget 自帶padding 屬性,所以不必多套一層Padding部件。(比如ListViewGridViewContainerScrollViewButton

  3. 儘量使用const來定義常量。比如paddingcolorstyle 這些地方:

class Colours {
  static const Color text_dark = Color(0xFF333333);
}

Padding(
  padding: const EdgeInsets.all(8.0),
  child: Text(
  	"Test",
  	style: TextStyle(
      fontSize: 26.0,
      color: Colours.text_dark
    )
  )
)
複製程式碼
  1. Dart2中的new 關鍵字可選,所以就不要選了,哈哈!!

其實我在這中間遇到的小問題還有很多,有的暫時還沒有找到好的方法去解決。不過這才剛剛開始,希望Flutter越來越好

篇幅有限,那麼先分享以上11條Tips,如果本篇對你有所幫助,可以點贊支援!最後再次奉上Github地址:github.com/simplezhli/…

相關文章