做爲系列文章的第十七篇,本篇再一次帶來 Flutter 開發過程當中的實用技巧,讓你繼續彎道超車,全篇均爲我的的平常乾貨總結,以實用填坑爲主,讓你少走彎路狂飆車。android
前文:ios
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 get
。less
win
通常是在C:\Users\xxxxx\AppData\Roaming\Pub\Cache
路徑下有git
目錄。ide
mac
目錄在~/.pub-cache
。佈局
如上代碼所示,紅線部分表示,若是 controller
爲空,就賦值一個 TextEditingController
,這樣的寫法會致使以下圖所示問題:post
彈出鍵盤時輸入成功後,收起鍵盤時輸入的內容消失了! 這是由於鍵盤的彈出和收起都會觸發頁面 build
,而在 controller
爲 null
時,每次賦值的 TextEditingController
會致使 TextField
的 TextEditingValue
重置。
如上圖所示,由於當 TextField
的 controller
不爲空時,update 時是不會執行 value
的拷貝,因此爲了不這類問題,以下圖所示, 須要先在全局構建 TextEditingController
再賦值,若是 controller
爲空直接給 null 便可,避免 build
時每次重構 TextEditingController
。
如上圖所示,在以前第七篇的時候分析過,滑動列表內通常都會有 Scrollable
的存在,而 Scrollable
剛好是一個 InheritedWidget
,這就給咱們在 children
中調用 Scrollable
相關方法提供了便利。
以下代碼所依,經過 Scrollable.of(context)
咱們能夠更解耦的在 ListView/GridView
的 children
對其進行控制。
ScrollableState state = Scrollable.of(context)
///獲取 _scrollable 內 viewport 的 renderObject
RenderObject renderObject = state.context.findRenderObject();
///監聽位置更新
state.position.addListener((){});
///通知位置更新
state.position.notifyListeners();
///滾動到指定位置
state.position.jumpTo(1000);
····
複製代碼
在 Flutter 中,提供了 BackdropFilter
和 ImageFilter
實現了高斯模糊的支持,以下代碼所示,能夠快速實現上圖的高斯模糊效果。
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
找到所需 item
的 RenderBox
,而後使用 localToGlobal
獲取 item
在 ViewPort
這個 ancestor
中的偏移量進行滾動:
固然還有另一種實現方式,具體可見 scroll_to_index_demo_page.dart
在 Flutter 中是存在 容器 Widget 和 渲染Widget 的區別的,通常狀況下:
Text
、Sliver
、ListTile
等都是屬於渲染 Widget ,其內部主要是 RenderObjectElement
。StatelessWidget
/ StatefulWidget
等屬於容器 Widget ,其內部使用的是 ComponentElement
, ComponentElement
自己是不存在 RenderObject
的。結合前面篇章咱們說過 BuildContext
的實現就是 Element
,因此 context.findRenderObject()
這個操做其實就是 Element
的 findRenderObject()
。
那麼如上圖所示,findRenderObject
的實現最終就是獲取 renderObject
,在 Element
中 renderObject
的獲取邏輯就很清晰了,在遇到 ComponentElement
時,執行的是 element.visitChildren(visit);
, 遞歸直到找到 RenderObjectElement
。
因此以下代碼所示,print("${globalKey.currentContext.findRenderObject()}");
最終輸出了 SizedBox
的 RenderObject
。
在 Flutter 中,是沒有直接設置 Text
行間距的方法的, Text
顯示的效果是以下圖所示的邏輯組成:
那麼咱們應該如何處理行間距呢?以下圖所示,經過設置 StrutStyle
的 leading
, 而後利用 Transform
作計算翻方向位置偏移,由於 leading
是上下均衡的,因此計算後就能夠獲得咱們所須要的行間距大小。 (雖然沒法保證必定 100%像素準確,你是否還知道其餘方法?)
這裏額外提一點,能夠經過父節點使用
DefaultTextStyle
來實現局部樣式的共享哦。
在 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 ,全局指定改字體顯示。
修改主題下全部 TextTheme
的 fontFamilyFallback
:
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);
}
複製代碼
自此,第十七篇終於結束了!(///▽///)