Flutter完整開發實戰詳解(十7、 實用技巧與填坑二)

做爲系列文章的第十七篇,本篇再一次帶來 Flutter 開發過程當中的實用技巧,讓你繼續彎道超車,全篇均爲我的的平常乾貨總結,以實用填坑爲主,讓你少走彎路狂飆車。android

前文:ios

一、Package get git 失敗

Flutter 項目在引用第三庫時,通常都是直接引用 pub 上的第三方插件,可是有時候咱們爲了安全和私密,會選擇使用 git 引用,如:git

photo_view:
    git:
      url: https://github.com/CarSmallGuo/photo_view.git
      ref: master
複製代碼

這時候在執行 flutter packages get 過程當中,若是出現失敗後,再次執行 flutter packages get 可能會遇到以下圖所示的問題:github

)

flutter packages get 提示 git 失敗的緣由,主要是:安全

在下載包的過程當中出現問題,下次再拉包的時候,.pub_cache 內的 git 目錄下會檢測到已經存在目錄,可是多是空目錄等等,致使 flutter packages get 的時候異常。bash

因此你須要清除掉 .pub_cache 內的 git 的異常目錄,而後最好清除掉項目下的 pubspec.lock ,以後從新執行 flutter packages getless

win 通常是在 C:\Users\xxxxx\AppData\Roaming\Pub\Cache 路徑下有 git 目錄。ide

mac 目錄在 ~/.pub-cache佈局

二、TextEditingController

image.png

如上代碼所示,紅線部分表示,若是 controller 爲空,就賦值一個 TextEditingController ,這樣的寫法會致使以下圖所示問題:post

彈出鍵盤時輸入成功後,收起鍵盤時輸入的內容消失了! 這是由於鍵盤的彈出和收起都會觸發頁面 build ,而在 controllernull 時,每次賦值的 TextEditingController 會致使 TextFieldTextEditingValue 重置。

image.png

如上圖所示,由於當 TextFieldcontroller 不爲空時,update 時是不會執行 value 的拷貝,因此爲了不這類問題,以下圖所示, 須要先在全局構建 TextEditingController 再賦值,若是 controller 爲空直接給 null 便可,避免 build 時每次重構 TextEditingController

三、Scrollable

如上圖所示,在以前第七篇的時候分析過,滑動列表內通常都會有 Scrollable 的存在,而 Scrollable 剛好是一個 InheritedWidget ,這就給咱們在 children 中調用 Scrollable 相關方法提供了便利。

以下代碼所依,經過 Scrollable.of(context) 咱們能夠更解耦的在 ListView/GridViewchildren 對其進行控制。

ScrollableState state = Scrollable.of(context)

///獲取 _scrollable 內 viewport 的 renderObject
RenderObject renderObject = state.context.findRenderObject();
///監聽位置更新
state.position.addListener((){});
///通知位置更新
state.position.notifyListeners();
///滾動到指定位置
state.position.jumpTo(1000);
····

複製代碼

四、圖片高斯模糊

在 Flutter 中,提供了 BackdropFilterImageFilter 實現了高斯模糊的支持,以下代碼所示,能夠快速實現上圖的高斯模糊效果。

class BlurDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new Container(
        child: Stack(
          children: <Widget>[
            Positioned(
              top: 0,
              bottom: 0,
              left: 0,
              right: 0,
              child: new Image.asset(
                "static/gsy_cat.png",
                fit: BoxFit.cover,
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
              )),
            new Center(
              child: new Container(
                width: 200,
                height: 200,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(15.0),
                  child: BackdropFilter(
                    filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
                    child: new Row(
                      mainAxisSize: MainAxisSize.max,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        new Icon(Icons.ac_unit),
                        new Text("哇!!")
                      ],
                    )))))
          ],
        )));
  }
}
複製代碼

五、滾動到指定位置

由於目前 Flutter 並無直接提供滾動到指定 Item 的方法,在每一個 Item 大小不一的狀況下,折中利用如圖下所示代碼,能夠快速實現滾動到指定 Item 的效果:

上圖爲部分代碼,完整代碼可見 scroll_to_index_demo_page2.dart ,這裏主要是給每一個 item 都賦予了一個 GlobalKey , 利用 findRenderObject 找到所需 itemRenderBox ,而後使用 localToGlobal 獲取 itemViewPort 這個 ancestor 中的偏移量進行滾動:

固然還有另一種實現方式,具體可見 scroll_to_index_demo_page.dart

六、findRenderObject

在 Flutter 中是存在 容器 Widget渲染Widget 的區別的,通常狀況下:

  • TextSliverListTile 等都是屬於渲染 Widget ,其內部主要是 RenderObjectElement
  • StatelessWidget / StatefulWidget 等屬於容器 Widget ,其內部使用的是 ComponentElementComponentElement 自己是不存在 RenderObject 的。

結合前面篇章咱們說過 BuildContext 的實現就是 Element,因此 context.findRenderObject() 這個操做其實就是 ElementfindRenderObject()

那麼如上圖所示,findRenderObject 的實現最終就是獲取 renderObject,在 ElementrenderObject 的獲取邏輯就很清晰了,在遇到 ComponentElement 時,執行的是 element.visitChildren(visit); , 遞歸直到找到 RenderObjectElement

因此以下代碼所示,print("${globalKey.currentContext.findRenderObject()}"); 最終輸出了 SizedBoxRenderObject

七、行間距

在 Flutter 中,是沒有直接設置 Text 行間距的方法的, Text 顯示的效果是以下圖所示的邏輯組成:

那麼咱們應該如何處理行間距呢?以下圖所示,經過設置 StrutStyleleading , 而後利用 Transform 作計算翻方向位置偏移,由於 leading 是上下均衡的,因此計算後就能夠獲得咱們所須要的行間距大小。 (雖然沒法保證必定 100%像素準確,你是否還知道其餘方法?)

這裏額外提一點,能夠經過父節點使用 DefaultTextStyle 來實現局部樣式的共享哦。

八、Builder

在 Flutter 中存在 Builder 這樣一個 Widget,看源碼發現它其實就是 StatelessWidget 的簡單封裝,那爲何還須要它的存在呢?

以下圖所示,相信一些 Flutter 開發者在使用 Scaffold.of(context).showSnackBar(snackbar) 時,可能 遇到過以下錯誤,這是由於傳入的 context 屬於錯誤節點致使的,由於此處傳入的 context 並不能找到頁面所在的 Scaffold 節點。

因此這時候 Builder 的做用就體現了,以下所示,經過 builder 方法返回賦予的 context ,在向上查找 Scaffold 的時候,就能夠順利找到父節點的 Scaffold 了,這也必定程度上體現了 ComponentElement 的做用之一。

九、快速實現動畫切換效果

要實現如上圖所示動畫效果,在 Flutter 中提供了 AnimatedSwitcher 封裝簡易實現。

以下圖所示,經過嵌套 AnimatedSwitcher ,指定 transitionBuilder 動畫效果,而後在數據改變時,同時改變須要執行動畫的 key 值,便可達到動畫切換的效果。

十、多語言顯示異常

在官方的 github.com/flutter/flu… issue 中能夠發現,Flutter 在韓語/日語 與中文同時顯示,會致使 iOS 下出現文字渲染異常的問題 ,以下圖所示,左邊爲異常狀況。

改問題解決方案暫時有兩種:

  • 增長字體 ttf ,全局指定改字體顯示。

  • 修改主題下全部 TextThemefontFamilyFallback

getThemeData() {
  var themeData = ThemeData(
        primarySwatch: primarySwatch
   );

    var result = themeData.copyWith(
      textTheme: confirmTextTheme(themeData.textTheme),
      accentTextTheme: confirmTextTheme(themeData.accentTextTheme),
      primaryTextTheme: confirmTextTheme(themeData.primaryTextTheme),
    );
    return result;
}
/// 處理 ios 上,同頁面出現韓文和簡體中文,致使的顯示字體異常
confirmTextTheme(TextTheme textTheme) {
  getCopyTextStyle(TextStyle textStyle) {
    return textStyle.copyWith(fontFamilyFallback: ["PingFang SC", "Heiti SC"]);
  }

  return textTheme.copyWith(
    display4: getCopyTextStyle(textTheme.display4),
    display3: getCopyTextStyle(textTheme.display3),
    display2: getCopyTextStyle(textTheme.display2),
    display1: getCopyTextStyle(textTheme.display1),
    headline: getCopyTextStyle(textTheme.headline),
    title: getCopyTextStyle(textTheme.title),
    subhead: getCopyTextStyle(textTheme.subhead),
    body2: getCopyTextStyle(textTheme.body2),
    body1: getCopyTextStyle(textTheme.body1),
    caption: getCopyTextStyle(textTheme.caption),
    button: getCopyTextStyle(textTheme.button),
    subtitle: getCopyTextStyle(textTheme.subtitle),
    overline: getCopyTextStyle(textTheme.overline),
  );
}
複製代碼

ps :經過WidgetsBinding.instance.window.locale; 能夠獲取到手機平臺自己的當前語言狀況,不須要 context ,也不是你設置後的 Locale

十一、長按輸入框致使異常的狀況

若是項目存在多語言和主題切換的場景,可能會遇到長按輸入框致使異常的場景,目前可推薦兩種解放方法:

  • 一、能夠給你的自定義 ThemeData 強制指定固定一個平臺,可是該方式會致使平臺複製粘貼彈出框沒有了平臺特性:
///防止輸入框長按崩潰問題
platform: TargetPlatform.android
複製代碼
  • 二、增長一個自定義的 LocalizationsDelegate , 實現多語言環境下的自定義支持:
class FallbackCupertinoLocalisationsDelegate
    extends LocalizationsDelegate<CupertinoLocalizations> {
  const FallbackCupertinoLocalisationsDelegate();

  @override
  bool isSupported(Locale locale) => true;

  @override
  Future<CupertinoLocalizations> load(Locale locale) => loadCupertinoLocalizations(locale);

  @override
  bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}

class CustomZhCupertinoLocalizations extends DefaultCupertinoLocalizations {
  const CustomZhCupertinoLocalizations();

  @override
  String datePickerMinuteSemanticsLabel(int minute) {
    if (minute == 1) return '1 分鐘';
    return minute.toString() + ' 分鐘';
  }

  @override
  String get anteMeridiemAbbreviation => '上午';

  @override
  String get postMeridiemAbbreviation => '下午';

  @override
  String get alertDialogLabel => '警告';

  @override
  String timerPickerHourLabel(int hour) => '小時';

  @override
  String timerPickerMinuteLabel(int minute) => '分';

  @override
  String timerPickerSecond(int second) => '秒';

  @override
  String get cutButtonLabel => '裁剪';

  @override
  String get copyButtonLabel => '複製';

  @override
  String get pasteButtonLabel => '粘貼';

  @override
  String get selectAllButtonLabel => '全選';
}

class CustomTCCupertinoLocalizations extends DefaultCupertinoLocalizations {
  const CustomTCCupertinoLocalizations();

  @override
  String datePickerMinuteSemanticsLabel(int minute) {
    if (minute == 1) return '1 分鐘';
    return minute.toString() + ' 分鐘';
  }

  @override
  String get anteMeridiemAbbreviation => '上午';

  @override
  String get postMeridiemAbbreviation => '下午';

  @override
  String get alertDialogLabel => '警告';

  @override
  String timerPickerHourLabel(int hour) => '小時';

  @override
  String timerPickerMinuteLabel(int minute) => '分';

  @override
  String timerPickerSecond(int second) => '秒';

  @override
  String get cutButtonLabel => '裁剪';

  @override
  String get copyButtonLabel => '復制';

  @override
  String get pasteButtonLabel => '粘貼';

  @override
  String get selectAllButtonLabel => '全選';
}

Future<CupertinoLocalizations> loadCupertinoLocalizations(Locale locale) {
  CupertinoLocalizations localizations;
  if (locale.languageCode == "zh") {
    switch (locale.countryCode) {
      case 'HK':
      case 'TW':
        localizations = CustomTCCupertinoLocalizations();
        break;
      default:
        localizations = CustomZhCupertinoLocalizations();
    }
  } else {
    localizations = DefaultCupertinoLocalizations();
  }
  return SynchronousFuture<CupertinoLocalizations>(localizations);
}

複製代碼

自此,第十七篇終於結束了!(///▽///)

資源推薦

文章

《Flutter完整開發實戰詳解系列》

《移動端跨平臺開發的深度解析》

相關文章
相關標籤/搜索