Flutter實戰之開發問題集(一)

JulyYu發表於2020-03-25

問題集每篇記錄設定在十個左右。絕大多數都是自己在Flutter實際開發中遇到的情況和解決辦法,加深在開發過程中面對問題的思考和思路以及日後方便查閱做備份同時也希望能為大家帶來幫助。

Flutter中文字和英文字高度顯示不同

這就存在一個問題,在UI佈局如果為Text設定Padding的時候會出現中英文文字顯示高度不一致的情況。難怪設計師老是找我茬,總算知道問題所在了。

Flutter實戰之開發問題集(一)
解決辦法其實很簡單在TextStyle中將height設為1就能可以了。

TextStyle(
    fontSize: 15,
    fontFamily: "",
    height: 1,
)
複製程式碼

實際中會發現中文顯示會超出實際高度,但這裡我們是為了保持統一高度以便於對外邊框做設定,所以超出部分並不影響正常使用。

Flutter實戰之開發問題集(一)

滑動元件在Android和iOS不同表現

由於Android和iOS平臺UI規範不同導致滑動元件互動性存在差異。例如在Android平臺在邊界是波紋動畫互動而iOS平臺邊界是回彈阻尼效果。所開發者希望統一互動效果可用ClampingScrollPhysics(Android)或是BouncingScrollPhysics(iOS)兩者替換SingleChildScrollView的physics。

SingleChildScrollView(
    physics: BouncingScrollPhysics(), ///ClampingScrollPhysics
)
複製程式碼

Flutter實戰之開發問題集(一)

設計師請不要切這麼多Icon

Android的ImageView有個屬性為tint可以渲染圖片顏色,但前提是渲染的圖片為單色。那麼在Flutter中Iamge的color也是同樣的作用,一方面可以減少切圖量一方面方便開發後期調色。之前一些Icon在可點選和不可點選呈現顏色是不同,UI在切圖上是同樣圖片切了兩份給我,由於切圖非單色導致渲染圖片顏色失敗。

Flutter實戰之開發問題集(一)

Image.asset(
   "res/drawable/ic_circle_company.png",
   height: 40,
   width: 40,
   color:Colors.red,
),
複製程式碼

單色切圖可以通過設定不同顏色實現顏色替換,所以有狀態的圖示儘量讓設計師切成單色剩下交給開發者自由發揮。

Flutter實戰之開發問題集(一)

雖然這不算是真正的開發問題,但也算是開發過程中遇到的有意義的問題所以還是想記錄一下,對於重複和可優化的點我們是不是應該儘量最簡化呢。另外減少冗餘資源佔用降低應用包大小。

關於路由不要只會用pop了

timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
  if (num == 0) {
    timer.cancel();
    Navigator.of(context).pop();
    bus.emit(EventBus.privateEvent);
  } else {
    num--;
    setState(() {});
  }
});
複製程式碼

如上程式碼閃屏頁倒數計時關閉邏輯,因為還存在其他非同步的業務邏輯比如在閃屏頁面出現登入退出彈出登入頁面。然後倒數計時結束呼叫Navigator.of(context).pop時退出的是路由棧最上登入頁,導致路由棧頁面出現問題無法手動退出閃屏頁同時進入不到首頁(預先閃屏頁已經設定了禁止退出)。因此使用removeRoute用於關閉當前頁面。

瞭解更多關於Flutter路由可以看看Flutter實戰之路由功能篇

timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
  if (num == 0) {
    timer.cancel();
    Navigator.of(context).removeRoute(ModalRoute.of(context));
    bus.emit(EventBus.privateEvent);
  } else {
    num--;
    setState(() {});
  }
});
複製程式碼

TextField使用真要命

在特殊情況下使用TextField問題,例如在Row巢狀TextField要有Expanded約束寬度。

展示佈局失敗

一些特殊情況會導致佈局構建失敗,例如使用外層加入Row包裝時會報錯。

Row(
  children: <Widget>[
    Image.asset("res/img/ic_star.png"),
    TextField(
        cursorColor: Colors.grey,
        decoration: InputDecoration(
          hintText: "HHHHHH",
          border: InputBorder.none,
          contentPadding: EdgeInsets.all(0),
          isDense: true,
        ),
      ),
  ],
)
複製程式碼

An InputDecorator, which is typically created by a TextField, cannot have an unbounded width 這時只能在TextField外層巢狀Expanded解決。

Row(
  children: <Widget>[
    Image.asset("res/img/ic_star.png"),
    Expanded(
      child: TextField(
        cursorColor: Colors.grey,
        decoration: InputDecoration(
          hintText: "HHHHHH",
          border: InputBorder.none,
          contentPadding: EdgeInsets.all(0),
          isDense: true,
        ),
      ),
    ),
  ],
)
複製程式碼

獲取焦點彈起軟鍵盤

通常情況輸入框預設獲取焦點彈起軟鍵盤的方式。

TextField(
  autofocus: true,
)
複製程式碼

但也存在輸入框焦點預設還是未獲取軟鍵盤沒彈出的情況,那麼再通過設定focusNode實現。

SchedulerBinding.instance.addPostFrameCallback((_) {
   FocusScope.of(context).requestFocus(_focusNode);
});
TextField(
   autofocus: true,
   focusNode: _focusNode,
)
複製程式碼

最後在退出頁面時記得焦點移除收回軟鍵盤

FocusScope.of(context).requestFocus(FocusNode());
複製程式碼

輸入框與其他元件顯示高度問題

因為原生輸入框樣式並不滿足日常業務開發並自定義輸入框採用了Container巢狀輸入框增加外邊框樣式。如下程式碼導致輸入框和其他元件高度不再統一水平線上。

Flutter實戰之開發問題集(一)

Container(
  margin: EdgeInsets.symmetric(horizontal: 10),
  padding: EdgeInsets.symmetric(horizontal: 10),
  alignment: Alignment.centerLeft,
  height: 30,
  decoration: new BoxDecoration(
    color: Colors.blueGrey,
    borderRadius: new BorderRadius.circular(4),
  ),
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: <Widget>[
      Image.asset(
        "res/img/ic_star.png",
        width: 10,
      ),
      Expanded(
        child: TextField(
          cursorColor: Colors.grey,
          autofocus: true,
          decoration: InputDecoration(
              hintText: "HHHHHH",
          ),
          showCursor: true,
          textInputAction: TextInputAction.search,
        ),
      ),
    ],
  ),
),
複製程式碼

最後設定如下程式碼解決,isDense為true、contentPadding都設為0。

TextField(
  cursorColor: Colors.grey,
  decoration: InputDecoration(
      hintText: "HHHHHH",
      border: InputBorder.none,
      contentPadding: EdgeInsets.all(0),
      isDense: true,
      hintStyle: TextStyle(fontSize: 15)),
  style: TextStyle(fontSize: 15),
  showCursor: true,
),
複製程式碼

Flutter網路請求無法抓包情況

測試同學說我開發應用無法抓包,想對測試資料進行監控和問題排查帶來困惑。事實上是Flutter開發中網路請求預設無法直接抓包。

Flutter升級到V1.12.3 iOS包大小問題

之前開發一直使用FlutterV1.9的版本,因為FlutterBoost有升級到支援V1.12.3-hotfix所以想作死一把將Flutter更新上去。前期升級很順利沒遇到啥問題基本上無任何需要修改配置的地方,直到在進行iOS打包時問題發生了。原先iOS打包後包大小在30M左右,現在包大小在100M左右。

確認再三我打包為release版本後趕緊在github上看issue,果真遇到這個問題的人不只我一個。該問題不是Flutter的bug而是1.12版本開始Flutter引擎包含了LLVM IR (bitcode)導致包大小發生變化。

同時將ios打包的ipa檔案上傳到App Store Connect後平臺會自動將安裝包壓縮至正常大小。但目前還不瞭解如何做到不上傳到store也把包大小壓縮下來,後期有時間再研究研究。

Flutter實戰之開發問題集(一)
Flutter實戰之開發問題集(一)

頁面路由返回值處理

頁面返回除了appbar的返回鍵外還有側滑操作。在側滑操作返回通過路由傳遞引數同樣需要攔截側滑返回操作自定義路由彈出操作傳遞需要的引數。這裡就用到WillPopScope元件將onWillPop返回值設為Future.value(false),然後自定義設定回參值。因此若有業務需求都需要返回值時則要特別注意這一點。但你如果使用了redux全域性狀態管理估計可以忽略這個問題了?

但從另一方面來看通過路由方式傳參並不是特別酸爽,一些特殊情況下還需要考慮多種情況。或許全域性狀態管理確實是另一種更好的選擇。

PS: 同時WillPopScope也適用於loading載入時禁止側滑退出等操作

onWillPop: () {
    Navigator.of(context).pop({
      "code": industryCode,
      "filter": vm.filters.length > 0 ? vm.filters : "",
    });
    return Future.value(false);
  },
複製程式碼

iPhoneX底部黑棒棒適配

Flutter實戰之開發問題集(一)
底部導航欄預設支援iOS底部橫條的適配,但自己寫的佈局就可能存在問題啦。例如使用Stack在底部Position元件很有可能元件顯示內容被橫條擋住。

Flutter實戰之開發問題集(一)

Scaffold(
  appBar: AppBar(),
  body: Container(
    color: Colors.green,
    child: Stack(
      children: <Widget>[
        Positioned(
          bottom: 0,
          child: Text("LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"),
        )
      ],
    ),
  ),
);
複製程式碼

檢視BottomNavigationBar原始碼會發現下面一段程式碼,通過MediaQuery.of(context).padding獲取螢幕底部內邊距,在BottomNav佈局中設定Padding來避免被橫條遮擋。

  // Labels apply up to _bottomMargin padding. Remainder is media padding.
    final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - widget.selectedFontSize / 2.0, 0.0);
/// 省略了部分無用程式碼 
.... 
Padding(
  padding: EdgeInsets.only(bottom: additionalBottomPadding),
  child: MediaQuery.removePadding(
    context: context,
    removeBottom: true,
    child: _createContainer(_createTiles()),
  ),
 ),
複製程式碼

因此對ios橫條適配需要對距底部的元件在加上MediaQuery.of(context).padding.bottom間距。當然對於iPhoneX的bottom值才大於0,像iPhone6獲取到的bottom值則為0以及android也是如此(具體情況看手機型號和機型而定)。

相關文章