學習Flutter也有一陣子了。閒着沒事,用了公司一個已經涼涼的App設計圖來練手。固然了接口不可能用的了,因此都是些死數據,實現效果能夠說是很完美了(獲得了設計的承認。。。)。固然本身也是邊查邊寫,也借鑑了許多Github上優秀的Flutter項目。如今開源出來(附帶設計圖),供你們交流學習。但願多多Star、Fork支持,有問題能夠Issue。附上連接:github.com/simplezhli/…html
本篇主要分享一下本身在此項目中遇到的問題及心得,但願對你有所幫助!java
異常大體以下:android
A RenderFlex overflowed by 22 pixels on the bottom.
複製代碼
致使的緣由就是在水平或者垂直方向上的內容超過了父部件的大小。通常來講咱們的頁面不存在這樣的問題,由於根據頁面的設計,事先能夠預料到是否超出。不過要注意到有輸入法彈出的頁面。好比我下面的這個例子:git
能夠看到底部溢出了22個像素,可能在18:9的手機以上不太會出現這種問題,由於屏幕的高度足夠。可是這種16:9的手機可能會暴露出來。解決的方法有兩種:github
包一層SingleChildScrollView
,讓你的頁面能夠滑動起來。web
在Scaffold
中設置resizeToAvoidBottomInset
爲false。默認爲ture,防止部件被遮擋。若是使用了這個方法,若是底部有輸入框,則會形成遮擋。緩存
你們能夠根據實際需求選擇。app
頁面以下:less
底部有輸入框,同時「提交」的按鈕固定在底部。一開始以爲既然固定在底部,那就使用Stack
配合Positioned
來實現,然而就致使輸入法彈出時,發生遮擋。ide
上圖中,我選中了最後一個輸入框,但由於輸入法默認都是在輸入框的下方彈出,然而上面蓋着這個「提交」按鈕,發生了遮擋。
最終個人解決方法就是使用Column
配合Expanded
來實現。修復後以下:
一旦有部件固定在頂部或者底部(嚴謹點的話能夠說是在屏幕的四邊)。那我咱們最好使用SafeArea
來包一下。由於Android 和 IOS都有狀態欄,甚至IOS還有叫作「HomeIndicator」的橫條。因此一不留神就會出現適配問題。
咱們在Flutter中常使用的BottomNavigationBar
和 AppBar
其實就在內部處理了此類問題。以 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 在開發中,讓人詬病的就是大量的嵌套,而咱們只能儘可能避免。好比將一些部件、屬性進行封裝,避免重複的書寫。不過封裝也講究使用場景。若是這種樣式的部件僅僅只是某一兩處使用,封裝顯得有點小題大作。而且封裝的大而全也會增長使用的複雜度。那麼這時就可使用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的地方。
注意部分組件在Android與IOS平臺之間的差別。
Scaffold
的 AppBar
,AppBar
中默認的title
在Android中靠左顯示,IOS中居中顯示。若是須要兩個平臺效果統一,須要設置在AppBar
中主動設置centerTitle
屬性。同時AppBar
的返回箭頭圖標也不相同,統一的話須要自定義leading
。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
)
),
...
);
}
}
複製代碼
ScrollPhysics效果,能夠滑動的部件都有一個physics
屬性。滑動到邊界時,Android平臺爲邊緣陰影的效果ClampingScrollPhysics
,IOS爲回彈效果BouncingScrollPhysics
。若是須要統一,能夠指定physics
屬性。
狀態欄方面,Android平臺默認是半透明的效果,IOS則是透明效果。好比Android要實現IOS的效果,能夠設置狀態欄爲透明。不過IOS要實現Android的效果則不行。。。,難道只能自定義?有知道方法的能夠分享一下。
void main(){
runApp(MyApp());
// 透明狀態欄
if (Platform.isAndroid) {
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
}
複製代碼
當TextField
的keyboardType
屬性設置爲TextInputType.phone
或TextInputType.number
時,IOS系統彈出的數字輸入鍵盤沒有"完成"按鈕,致使輸入法沒法關閉。固然了Android不存在這個問題。
比較成熟有效的方案是在鍵盤彈出的上方懸浮一個按鈕,點擊能夠關閉鍵盤。固然了,這種問題也有對應的庫能夠解決,我使用的是flutter_keyboard_actions來解決了這個問題。由於在Android端我發現了部分輸入法的兼容問題,因此只針對IOS作了處理。你們能夠看一下先後對比圖,具體實現代碼能夠參考flutter_keyboard_actions
的文檔和個人項目代碼:
固然平臺差別不只僅是這麼多,好比IOS自帶側滑返回等。具體咱們能夠去查看調用TargetPlatform
枚舉類的代碼。
若是你以爲這樣真麻煩,我給你支個大招,修改ThemeData
的platform
,指定一個平臺。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
platform: TargetPlatform.android
),
...
);
}
}
複製代碼
其次就是使用TextInputType.number
在IOS中彈起的鍵盤沒有小數點符號。在輸入金額類型數據時,須要將keyboardType
屬性設置爲TextInputType.numberWithOptions(decimal: true)
。
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),
);
}
}
複製代碼
InkWell
有的叫濺墨效果,有的叫水波紋效果。使用場景是給一些無點擊事件的部件添加點擊事件時使用(也支持長按、雙擊等事件),同時你也能夠去修改它的顏色和形狀。
InkWell(
borderRadius: BorderRadius.circular(8.0), // 圓角
splashColor: Colors.transparent, // 濺墨色(波紋色)
highlightColor: Colors.transparent, // 點擊時的背景色(高亮色)
onTap: () {},// 點擊事件
child: Container(),
);
複製代碼
不過有時你會發現並非包一層InkWell
就必定會有濺墨效果。主要緣由是濺墨效果是在一個背景效果,並非覆蓋的前景效果。因此InkWell
中的child一旦有設置背景圖或背景色,那麼就會遮住這個濺墨效果。若是你須要這個濺墨效果,有兩種方式實現。
Material
,將背景色設置在 Material
中的color裏。Material(
color: Colors.white,
child: InkWell(),
)
複製代碼
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: () {},
),
),
)
],
)
複製代碼
好比點擊導航欄來回切換頁面,默認狀況下會丟失原頁面狀態,也就是每次切換都會從新初始化頁面。這種狀況解決方法就是PageView
與BottomNavigationBar
結合使用,同時子頁面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 三種方式實現頁面切換後保持原頁面狀態
首先這裏建議凡是Flutter的插件在填寫版本號時不要使用^
符號。
^
符號意味着你可使用此插件的最新版本(大於等於當前版本)。這會致使什麼問題呢?可能你前一天代碼還能跑起來,今天就編譯出錯了。由於這些插件中包括Android、IOS的所用依賴環境配置,常見的就是新版本使用了AndroidX的依賴,可是還有些插件並無使用AndroidX,致使了二者的衝突。
我以前在看flutter-go
的代碼時,就是由於webview的插件忽然升級了,致使了安裝失敗。具體問題能夠看這裏。因此在代碼穩定的狀況下不建議使用^
符號。
發生了這種問題,有如下幾個解決方法:
使用非AndroidX的版本插件。(優勢就是見效快。缺點就是此插件後續的更新沒法使用)
手動修改插件的衝突,由於Flutter插件的代碼是能夠直接修改的,因此你能夠手動修改掉這些衝突,統一插件的版本(優勢就是可使用最新的版本。缺點就是這種方法首先麻煩,其次不利於團隊開發使用)
我偏好使用第二種,只要作好修改的相關記錄就行,算是一勞永逸。
打包自己流程沒有問題,配置好籤名文件,執行flutter build apk
命令。可是發現打包後沒有將插件中的AndroidManifest.xml
文件合併。好比我有使用image_picker
插件,它的AndroidManifest.xml
文件以下:
能夠看到有權限的及Android 7.0FileProvider
的聲明。諸如此類的信息沒有打包進去(可是引用xml中的flutter_image_picker_file_paths
文件卻在),致使我實際使用這些功能時沒有反應,可是在平時的調試過程當中倒是好的。
中間我發現打包後的App名稱也是以前的,懷疑是緩存問題,因此我手動刪除了項目根目錄的build
與.gradle
文件夾,從新打包就行了。因此打包後最好檢查一下AndroidManifest.xml
文件,避免此類緩存形成的問題。
Container
功能強大,設置寬高、padding、margin、背景色、背景圖、圓角、陰影等均可以使用它。
有些widget
自帶padding
屬性,因此沒必要多套一層Padding
部件。(好比ListView
、GridView
、Container
、ScrollView
、Button
)
儘可能使用const
來定義常量。好比padding
、color
、style
這些地方:
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
)
)
)
複製代碼
new
關鍵字可選,因此就不要選了,哈哈!!緊接下一篇:Flutter開發中的一些Tips(二)
其實我在這中間遇到的小問題還有不少,有的暫時尚未找到好的方法去解決。不過這纔剛剛開始,但願Flutter愈來愈好。
篇幅有限,那麼先分享以上11條Tips,若是本篇對你有所幫助,能夠點贊支持!最後再次奉上Github地址:github.com/simplezhli/…