Flutter開發中的一些Tips

學習Flutter也有一陣子了。閒着沒事,用了公司一個已經涼涼的App設計圖來練手。固然了接口不可能用的了,因此都是些死數據,實現效果能夠說是很完美了(獲得了設計的承認。。。)。固然本身也是邊查邊寫,也借鑑了許多Github上優秀的Flutter項目。如今開源出來(附帶設計圖),供你們交流學習。但願多多Star、Fork支持,有問題能夠Issue。附上連接:github.com/simplezhli/…html

在這裏插入圖片描述

本篇主要分享一下本身在此項目中遇到的問題及心得,但願對你有所幫助!java

1.部件溢出

異常大體以下:android

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

致使的緣由就是在水平或者垂直方向上的內容超過了父部件的大小。通常來講咱們的頁面不存在這樣的問題,由於根據頁面的設計,事先能夠預料到是否超出。不過要注意到有輸入法彈出的頁面。好比我下面的這個例子:git

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

  1. 包一層SingleChildScrollView,讓你的頁面能夠滑動起來。web

  2. Scaffold中設置resizeToAvoidBottomInset爲false。默認爲ture,防止部件被遮擋。若是使用了這個方法,若是底部有輸入框,則會形成遮擋。緩存

你們能夠根據實際需求選擇。app

2.輸入框的遮擋

頁面以下:less

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

上圖中,我選中了最後一個輸入框,但由於輸入法默認都是在輸入框的下方彈出,然而上面蓋着這個「提交」按鈕,發生了遮擋。

最終個人解決方法就是使用Column配合Expanded來實現。修復後以下:

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(),
  ),
)		
複製代碼

仍是上面的頁面,咱們對比一下處理先後的效果:

4.善用Theme

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

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

這時咱們使用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的文檔和個人項目代碼:

固然平臺差別不只僅是這麼多,好比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開發中的一些Tips(二)

其實我在這中間遇到的小問題還有不少,有的暫時尚未找到好的方法去解決。不過這纔剛剛開始,但願Flutter愈來愈好

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

相關文章
相關標籤/搜索