學(xué)習(xí)Flutter也有一陣子了。閑著沒事迁匠,用了公司一個(gè)已經(jīng)涼涼的App設(shè)計(jì)圖來練手剩瓶。當(dāng)然了接口不可能用的了,所以都是些死數(shù)據(jù)城丧,實(shí)現(xiàn)效果可以說是很完美了(得到了設(shè)計(jì)的認(rèn)可延曙。。亡哄。)枝缔。當(dāng)然自己也是邊查邊寫,也借鑒了許多Github上優(yōu)秀的Flutter項(xiàng)目。現(xiàn)在開源出來(附帶設(shè)計(jì)圖)愿卸,供大家交流學(xué)習(xí)灵临。希望多多Star、Fork支持趴荸,有問題可以Issue儒溉。附上鏈接:https://github.com/simplezhli/flutter_deer
本篇主要分享一下自己在此項(xiàng)目中遇到的問題及心得,希望對(duì)你有所幫助发钝!
1.部件溢出
異常大致如下:
A RenderFlex overflowed by 22 pixels on the bottom.
導(dǎo)致的原因就是在水平或者垂直方向上的內(nèi)容超過了父部件的大小顿涣。一般來說我們的頁面不存在這樣的問題,因?yàn)楦鶕?jù)頁面的設(shè)計(jì)酝豪,事先可以預(yù)料到是否超出涛碑。不過要注意到有輸入法彈出的頁面。比如我下面的這個(gè)例子:
可以看到底部溢出了22個(gè)像素孵淘,可能在18:9的手機(jī)以上不太會(huì)出現(xiàn)這種問題蒲障,因?yàn)槠聊坏母叨茸銐颉5沁@種16:9的手機(jī)可能會(huì)暴露出來夺英。解決的方法有兩種:
包一層
SingleChildScrollView
晌涕,讓你的頁面可以滑動(dòng)起來。在
Scaffold
中設(shè)置resizeToAvoidBottomInset
為false痛悯。默認(rèn)為ture,防止部件被遮擋重窟。如果使用了這個(gè)方法载萌,如果底部有輸入框,則會(huì)造成遮擋巡扇。
大家可以根據(jù)實(shí)際需求選擇扭仁。
2.輸入框的遮擋
頁面如下:
底部有輸入框,同時(shí)“提交”的按鈕固定在底部厅翔。一開始覺得既然固定在底部乖坠,那就使用Stack
配合Positioned
來實(shí)現(xiàn),然而就導(dǎo)致輸入法彈出時(shí)刀闷,發(fā)生遮擋熊泵。
上圖中,我選中了最后一個(gè)輸入框甸昏,但因?yàn)檩斎敕J(rèn)都是在輸入框的下方彈出顽分,然而上面蓋著這個(gè)“提交”按鈕,發(fā)生了遮擋施蜜。
最終我的解決方法就是使用Column
配合Expanded
來實(shí)現(xiàn)卒蘸。修復(fù)后如下:
3.SafeArea
一旦有部件固定在頂部或者底部(嚴(yán)謹(jǐn)點(diǎn)的話可以說是在屏幕的四邊)。那我我們最好使用SafeArea
來包一下翻默。因?yàn)锳ndroid 和 IOS都有狀態(tài)欄缸沃,甚至IOS還有叫做“HomeIndicator”的橫條恰起。所以一不留神就會(huì)出現(xiàn)適配問題。
我們?cè)贔lutter中常使用的BottomNavigationBar
和 AppBar
其實(shí)就在內(nèi)部處理了此類問題趾牧。以 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( // 需要顏色填充到邊界區(qū)域可以使用
color: Colors.white,
child: SafeArea(
child: Container(),
),
)
還是上面的頁面村缸,我們對(duì)比一下處理前后的效果:
4.善用Theme
Flutter 在開發(fā)中,讓人詬病的就是大量的嵌套武氓,而我們只能盡量避免梯皿。比如將一些部件、屬性進(jìn)行封裝县恕,避免重復(fù)的書寫东羹。不過封裝也講究使用場(chǎng)景。如果這種樣式的部件僅僅只是某一兩處使用忠烛,封裝顯得有點(diǎn)小題大做钞脂。并且封裝的大而全也會(huì)增加使用的復(fù)雜度偎肃。那么這時(shí)就可以使用Theme這種辦法。
舉一個(gè)例子,在下圖中圈起來的部分有三個(gè)按鈕芬为,它們的高度相同,文字厌杜、圓角大小也相同榕栏。如果每一個(gè)都去設(shè)定這些屬性,未免太過麻煩胯陋。
這時(shí)我們使用Theme去統(tǒng)一修改它們的樣式蕊温,就會(huì)很方便了。
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("聯(lián)系客戶"),
),
......
FlatButton(
color: Color(0xFFF6F6F6),
onPressed: (){},
child: Text("拒單"),
)
],
),
)
同時(shí)使用Theme
還可以修改許多默認(rèn)的設(shè)置遏乔,比如FlatButton
的默認(rèn)寬度為88义矛,高度為36,但是FlatButton
中沒有直接修改的屬性盟萨,網(wǎng)上好多的方法都是通過包一層Container
去修改凉翻,不僅增加的嵌套,有些需求還不能達(dá)到捻激。所以善用Theme
可以讓你省時(shí)省力制轰,不過缺點(diǎn)就是你需要去翻翻源碼,尋找使用這些Theme的地方铺罢。
5.注意平臺(tái)差異
注意部分組件在Android與IOS平臺(tái)之間的差異艇挨。
-
Scaffold
的AppBar
,AppBar
中默認(rèn)的title
在Android中靠左顯示韭赘,IOS中居中顯示缩滨。如果需要兩個(gè)平臺(tái)效果統(tǒng)一,需要設(shè)置在AppBar
中主動(dòng)設(shè)置centerTitle
屬性。同時(shí)AppBar
的返回箭頭圖標(biāo)也不相同脉漏,統(tǒng)一的話需要自定義leading
苞冯。
- 頁面跳轉(zhuǎn)如果使用
MaterialPageRoute
來做過渡效果,注意Android中新的頁面會(huì)從屏幕底部滑動(dòng)到屏幕頂部侧巨,IOS中新的頁面會(huì)從屏幕右側(cè)滑動(dòng)到屏幕左側(cè)舅锄。
如果需要兩個(gè)平臺(tái)效果統(tǒng)一,我們不使用自帶效果司忱,可以自定義一個(gè)皇忿。
Navigator.push(context, PageRouteBuilder(transitionDuration: Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation){
return new FadeTransition( //使用漸隱漸入過渡,
opacity: animation,
child: TestPage(),
);
})
);
要么修改Theme
,統(tǒng)一兩平臺(tái)的實(shí)現(xiàn)坦仍。:
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效果鳍烁,可以滑動(dòng)的部件都有一個(gè)
physics
屬性》痹滑動(dòng)到邊界時(shí)幔荒,Android平臺(tái)為邊緣陰影的效果ClampingScrollPhysics
,IOS為回彈效果BouncingScrollPhysics
梳玫。如果需要統(tǒng)一爹梁,可以指定physics
屬性。狀態(tài)欄方面提澎,Android平臺(tái)默認(rèn)是半透明的效果姚垃,IOS則是透明效果。比如Android要實(shí)現(xiàn)IOS的效果虱朵,可以設(shè)置狀態(tài)欄為透明莉炉。不過IOS要實(shí)現(xiàn)Android的效果則不行。碴犬。。梆暮,難道只能自定義服协?有知道方法的可以分享一下。
void main(){
runApp(MyApp());
// 透明狀態(tài)欄
if (Platform.isAndroid) {
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
}
- 輸入鍵盤
當(dāng)TextField
的keyboardType
屬性設(shè)置為TextInputType.phone
或TextInputType.number
時(shí)啦粹,IOS系統(tǒng)彈出的數(shù)字輸入鍵盤沒有"完成"按鈕偿荷,導(dǎo)致輸入法無法關(guān)閉。當(dāng)然了Android不存在這個(gè)問題唠椭。
比較成熟有效的方案是在鍵盤彈出的上方懸浮一個(gè)按鈕跳纳,點(diǎn)擊可以關(guān)閉鍵盤。當(dāng)然了贪嫂,這種問題也有對(duì)應(yīng)的庫可以解決寺庄,我使用的是flutter_keyboard_actions來解決了這個(gè)問題。因?yàn)樵贏ndroid端我發(fā)現(xiàn)了部分輸入法的兼容問題,所以只針對(duì)IOS做了處理斗塘。大家可以看一下前后對(duì)比圖赢织,具體實(shí)現(xiàn)代碼可以參考flutter_keyboard_actions
的文檔和我的項(xiàng)目代碼:
當(dāng)然平臺(tái)差異不僅僅是這么多,比如IOS自帶側(cè)滑返回等馍盟。具體我們可以去查看調(diào)用TargetPlatform
枚舉類的代碼于置。
如果你覺得這樣真麻煩,我給你支個(gè)大招贞岭,修改ThemeData
的platform
八毯,指定一個(gè)平臺(tái)。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
platform: TargetPlatform.android
),
...
);
}
}
其次就是使用TextInputType.number
在IOS中彈起的鍵盤沒有小數(shù)點(diǎn)符號(hào)瞄桨。在輸入金額類型數(shù)據(jù)時(shí)话速,需要將keyboardType
屬性設(shè)置為TextInputType.numberWithOptions(decimal: true)
。
6.keyboardType
keyboardType
屬性主要含義為彈起的鍵盤類型讲婚,并不代表輸入數(shù)據(jù)的類型尿孔。
而在Android開發(fā)中,在EditText
中設(shè)置android:inputType
不僅可以指定彈起的鍵盤類型筹麸,同時(shí)也確定了輸入數(shù)據(jù)的類型活合,也就是內(nèi)置了數(shù)據(jù)的格式校驗(yàn)。Flutter中并沒有后者物赶,所以可能一開始你是TextInputType.number
白指,但是在輸入法中切換成中文鍵盤,一樣可以輸入中文字符酵紫。所以數(shù)據(jù)的校驗(yàn)需要我們使用inputFormatters
自己處理告嘲。
比如TextInputType.phone
時(shí)可以使用WhitelistingTextInputFormatter
白名單校驗(yàn),只允許輸入0~9:
TextField(
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter(RegExp("[0-9]"))]
)
輸入密碼時(shí)可以使用BlacklistingTextInputFormatter
黑名單校驗(yàn)奖地,除去中文字符:
TextField(
keyboardType: TextInputType.text,
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[\u4e00-\u9fa5]"))]
)
輸入小數(shù)時(shí)橄唬,可以自定義TextInputFormatter
來限制輸入小數(shù)格式:
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
有的叫濺墨效果,有的叫水波紋效果参歹。使用場(chǎng)景是給一些無點(diǎn)擊事件的部件添加點(diǎn)擊事件時(shí)使用(也支持長按仰楚、雙擊等事件),同時(shí)你也可以去修改它的顏色和形狀犬庇。
InkWell(
borderRadius: BorderRadius.circular(8.0), // 圓角
splashColor: Colors.transparent, // 濺墨色(波紋色)
highlightColor: Colors.transparent, // 點(diǎn)擊時(shí)的背景色(高亮色)
onTap: () {},// 點(diǎn)擊事件
child: Container(),
);
不過有時(shí)你會(huì)發(fā)現(xiàn)并不是包一層InkWell
就一定會(huì)有濺墨效果僧界。主要原因是濺墨效果是在一個(gè)背景效果,并不是覆蓋的前景效果臭挽。所以InkWell
中的child一旦有設(shè)置背景圖或背景色捂襟,那么就會(huì)遮住這個(gè)濺墨效果。如果你需要這個(gè)濺墨效果欢峰,有兩種方式實(shí)現(xiàn)葬荷。
- 包一層
Material
涨共,將背景色設(shè)置在Material
中的color里。
Material(
color: Colors.white,
child: InkWell(),
)
- 使用
Stack
布局闯狱,將InkWell
放置在上層煞赢。這種適用于給圖片添加點(diǎn)擊效果,比如Banner圖的點(diǎn)擊哄孤。
Stack(
children: <Widget>[
Positioned.fill(
child: Image(),
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
splashColor: Color(0X40FFFFFF),
highlightColor: Colors.transparent,
onTap: () {},
),
),
)
],
)
8.保持頁面狀態(tài)
比如點(diǎn)擊導(dǎo)航欄來回切換頁面照筑,默認(rèn)情況下會(huì)丟失原頁面狀態(tài),也就是每次切換都會(huì)重新初始化頁面瘦陈。這種情況解決方法就是PageView
與BottomNavigationBar
結(jié)合使用凝危,同時(shí)子頁面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;
}
詳細(xì)的可以看這篇文章:Flutter 三種方式實(shí)現(xiàn)頁面切換后保持原頁面狀態(tài)
9.依賴版本問題
首先這里建議凡是Flutter的插件在填寫版本號(hào)時(shí)不要使用^
符號(hào)晨逝。
^
符號(hào)意味著你可以使用此插件的最新版本(大于等于當(dāng)前版本)蛾默。這會(huì)導(dǎo)致什么問題呢?可能你前一天代碼還能跑起來捉貌,今天就編譯出錯(cuò)了支鸡。因?yàn)檫@些插件中包括Android、IOS的所用依賴環(huán)境配置趁窃,常見的就是新版本使用了AndroidX的依賴牧挣,但是還有些插件并沒有使用AndroidX,導(dǎo)致了兩者的沖突醒陆。
我之前在看flutter-go
的代碼時(shí)瀑构,就是因?yàn)閣ebview的插件突然升級(jí)了,導(dǎo)致了安裝失敗刨摩。具體問題可以看這里寺晌。所以在代碼穩(wěn)定的情況下不建議使用^
符號(hào)。
發(fā)生了這種問題澡刹,有以下幾個(gè)解決方法:
使用非AndroidX的版本插件呻征。(優(yōu)點(diǎn)就是見效快。缺點(diǎn)就是此插件后續(xù)的更新無法使用)
手動(dòng)修改插件的沖突罢浇,因?yàn)镕lutter插件的代碼是可以直接修改的怕犁,所以你可以手動(dòng)修改掉這些沖突,統(tǒng)一插件的版本(優(yōu)點(diǎn)就是可以使用最新的版本己莺。缺點(diǎn)就是這種方法首先麻煩,其次不利于團(tuán)隊(duì)開發(fā)使用)
我偏好使用第二種戈轿,只要做好修改的相關(guān)記錄就行凌受,算是一勞永逸。
10.Flutter Android 打包
打包本身流程沒有問題思杯,配置好簽名文件胜蛉,執(zhí)行flutter build apk
命令挠进。但是發(fā)現(xiàn)打包后沒有將插件中的AndroidManifest.xml
文件合并。比如我有使用image_picker
插件誊册,它的AndroidManifest.xml
文件如下:
可以看到有權(quán)限的及Android 7.0FileProvider
的聲明领突。諸如此類的信息沒有打包進(jìn)去(但是引用xml中的flutter_image_picker_file_paths
文件卻在),導(dǎo)致我實(shí)際使用這些功能時(shí)沒有反應(yīng)案怯,但是在平時(shí)的調(diào)試過程中卻是好的君旦。
中間我發(fā)現(xiàn)打包后的App名稱也是之前的,懷疑是緩存問題嘲碱,所以我手動(dòng)刪除了項(xiàng)目根目錄的build
與.gradle
文件夾金砍,重新打包就好了。所以打包后最好檢查一下AndroidManifest.xml
文件麦锯,避免此類緩存造成的問題恕稠。
11.其他
Container
功能強(qiáng)大,設(shè)置寬高扶欣、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
)
)
)
- Dart2中的
new
關(guān)鍵字可選扫夜,所以就不要選了,哈哈3刍病笤闯!
緊接下一篇:Flutter開發(fā)中的一些Tips(二)
其實(shí)我在這中間遇到的小問題還有很多,有的暫時(shí)還沒有找到好的方法去解決棍厂。不過這才剛剛開始颗味,希望Flutter越來越好。
篇幅有限牺弹,那么先分享以上11條Tips浦马,如果本篇對(duì)你有所幫助时呀,可以點(diǎn)贊支持!最后再次奉上Github地址:https://github.com/simplezhli/flutter_deer