原文作者:騰訊技術(shù)
原文鏈接:https://zhuanlan.zhihu.com/p/90836859
來(lái)源:知乎
本文真對(duì) Flutter 的技術(shù)特性慎框,做了一些略全面的入門(mén)級(jí)的介紹路狮,如果你聽(tīng)說(shuō)過(guò)Flutter莱衩,想去了解它悴晰,但是又不想去翻厚厚的API,那么本文就是為你準(zhǔn)備的枢泰。
隨著純客戶(hù)端到Hybrid技術(shù)描融,到RN&Weex,再到如今的Flutter技術(shù)宗苍,客戶(hù)端實(shí)現(xiàn)技術(shù)不斷前進(jìn)。 在之前的一個(gè)APP項(xiàng)目中薄榛,因?yàn)闅v史原因當(dāng)時(shí)選擇了weex讳窟,隨著使用的不斷深入,我們逐漸發(fā)現(xiàn)了weex的渲染性能問(wèn)題已經(jīng)成為一個(gè)隱患和瓶頸敞恋。 而Flutter技術(shù)的不斷成熟和流行丽啡,F(xiàn)lutter的良好的跨平臺(tái)性和高性能優(yōu)點(diǎn),不斷吸引著我們硬猫。
(本文包含以下內(nèi)容补箍,閱讀完需要約18分鐘)
1.Flutter是啥玩意兒?
2.移動(dòng)端跨平臺(tái)技術(shù)對(duì)比
2.1 H5+原生APP
2.2 RN&Weex
2.3 Flutter
3.Dart語(yǔ)言
4.環(huán)境配置
5.Hello World
5.1 創(chuàng)建項(xiàng)目
5.2 項(xiàng)目結(jié)構(gòu)
5.3 啟動(dòng)模擬器
5.4 啟動(dòng)項(xiàng)目APP
5.5 簡(jiǎn)化版的Hello World
5.6 給頁(yè)面加上狀態(tài)
5.7 小結(jié)一下
6.路由
6.1 單個(gè)頁(yè)面的跳轉(zhuǎn)
6.2 更多頁(yè)面跳轉(zhuǎn)使用路由表
6.3 路由傳參
7.widget
7.1 Text
7.2 Button
7.3 Container
7.4 Image
8.布局
8.1 Row & Column & Center 行列軸布局
8.2 Align 角定位布局
8.3 Stack & Positioned 絕對(duì)定位
8.4 Flex & Expanded 流式布局
9.動(dòng)畫(huà)
9.1 簡(jiǎn)單動(dòng)畫(huà):淡入淡出
9.2 復(fù)雜一些的動(dòng)畫(huà):放大縮小
10.http請(qǐng)求
10.1 HttpClient
10.2 http
10.3 Dio
11.吐吐槽
11.1 墻
11.2 組件過(guò)度設(shè)計(jì)
11.3 嵌套太多不適應(yīng)
11.4 布局修改會(huì)導(dǎo)致嵌套關(guān)系修改
11.5 Dart語(yǔ)言升級(jí)
11.6 不能熱更新
12.結(jié)語(yǔ)
1.Flutter是啥玩意兒啸蜜?
Flutter是谷歌的移動(dòng)UI框架坑雅,可以快速在iOS和Android上構(gòu)建高質(zhì)量的原生用戶(hù)界面。
具有跨平臺(tái)開(kāi)發(fā)特性衬横,支持IOS裹粤、Android、Web三端蜂林。
熱重載特性大大提高了開(kāi)發(fā)效率
自繪UI引擎和編譯成原生代碼的方式遥诉,使得系統(tǒng)的運(yùn)行時(shí)的高性能成為了可能
使用Dart語(yǔ)言,目前已經(jīng)支持同時(shí)編譯成Web端代碼噪叙,
到底值不值得跟進(jìn)Flutter技術(shù)呢矮锈? 還是看下Flutter,Weex睁蕾,ReactNative的搜索指數(shù)對(duì)比苞笨,大概就知道這個(gè)行業(yè)趨勢(shì)了。
藍(lán)色是Flutter,可以看出上升勢(shì)頭非常強(qiáng)勁猫缭。苦逼的前端就是這樣葱弟,你不跟潮流,潮流就會(huì)把你拋棄猜丹。
2.移動(dòng)端跨平臺(tái)技術(shù)對(duì)比
為啥會(huì)有Flutter這種東西? 他的原理是什么芝加? 他是怎么做到高性能的? 要明白這些問(wèn)題射窒,我們不得不從幾種移動(dòng)端跨平臺(tái)技術(shù)的對(duì)比講起藏杖。
2.1 H5+原生APP
技術(shù)門(mén)檻最低,接入速度最快脉顿,熱更新最方便的蝌麸,自然就是H5方式。APP中提供一個(gè)Webview使用H5頁(yè)面的Http直連艾疟。APP和H5可以相互獨(dú)立開(kāi)發(fā)来吩,JS使用Bridge與原生進(jìn)行數(shù)據(jù)通信,顯示界面依賴(lài)Webview的瀏覽器渲染蔽莱。 但是帶來(lái)的問(wèn)題也很明顯弟疆,因?yàn)槭切枰h(yuǎn)程直連,那么初次打開(kāi)H5頁(yè)面盗冷,會(huì)有瞬間的白屏怠苔,并且Webview本身會(huì)有至少幾十M的內(nèi)存消耗。
當(dāng)然仪糖,作為前端開(kāi)發(fā)人員柑司,在H5方式可以使用SPA單頁(yè)面、懶加載锅劝、離線(xiàn)H5等各種前端優(yōu)化手段進(jìn)行性能優(yōu)化攒驰,以使得H5的表現(xiàn)更接近原生。但是首次的瞬間白屏和內(nèi)存故爵,Bridge的通信效率低下讼育,始終是被技術(shù)框架給局限住了。
2.2 RN&Weex
由于H5的那些弊端稠集,愛(ài)折騰的前端工程師奶段,祭出了RN、Weex兩個(gè)大殺器剥纷, 使用原生去解析RN痹籍、Weex的顯示配置,顯示層晦鞋、邏輯層都直接與原生數(shù)據(jù)通信蹲缠。 因?yàn)閽仐壛藶g覽器棺克,自然渲染性能、執(zhí)行性能都提升了一大截线定。
但是娜谊,每次遇到顯示的變更,JS都還會(huì)通過(guò)Bridge和原生轉(zhuǎn)一道再做渲染的調(diào)整斤讥,所以Bridge就最后成為了性能的瓶頸纱皆。在實(shí)際項(xiàng)目中,特別是做一些大量復(fù)雜動(dòng)畫(huà)處理的時(shí)候芭商,由于渲染部分需要頻繁通信派草,性能問(wèn)題變得尤為突出。 有興趣的同學(xué)可以去看看BindingX铛楣,里面有關(guān)于動(dòng)畫(huà)中數(shù)據(jù)通信效率低下導(dǎo)致動(dòng)畫(huà)幀率低下的詳細(xì)說(shuō)明近迁。
2.3 Flutter
不得不佩服Google開(kāi)發(fā)人員的想象力,為了達(dá)到極致性能簸州,F(xiàn)lutter更前進(jìn)了一步鉴竭,F(xiàn)lutter代碼編譯完成以后,直接就是原生代碼岸浑,并且使用自繪UI引擎原生方式做渲染搏存。 Flutter依賴(lài)一個(gè)Skia 2D圖形化引擎。Skia也是Android平臺(tái)和Chrome的底層渲染引擎助琐,所以性能方面完全不用擔(dān)心祭埂。因?yàn)槭褂肈art做AOT編譯成原生面氓,自然也比使用解釋性的JS在V8引擎中執(zhí)行性能更快兵钮,并且因?yàn)槿サ鬊ridge,沒(méi)有了繁瑣的數(shù)據(jù)通信和交互舌界,性能就更前進(jìn)了一步掘譬。
3.Dart語(yǔ)言
學(xué)習(xí)Flutter,得先了解Dart呻拌。Dart語(yǔ)言曾經(jīng)雄心勃勃的要替換Javascript, 但是發(fā)布的時(shí)機(jī)正好遇到JS的飛速發(fā)展葱轩,于是就逐漸沉寂,直到配合Flutter的發(fā)布藐握,才又重新煥發(fā)了生機(jī)靴拱。
在最近2019年9月的一次Google開(kāi)發(fā)者大會(huì)中,伴隨著Flutter1.9的發(fā)布猾普,目前的Dart也同時(shí)更新到了2.5版本袜炕, 提供了機(jī)器學(xué)習(xí)和對(duì)C跨平臺(tái)調(diào)用的能力〕跫遥總體來(lái)說(shuō)偎窘,Dart語(yǔ)法乌助,對(duì)于前端同學(xué),上手還是很容易的陌知,風(fēng)格很像他托。
關(guān)于Dart語(yǔ)法,請(qǐng)移步傳送門(mén):https://dart.dev/samples
4.環(huán)境配置
無(wú)論學(xué)什么新語(yǔ)言仆葡,首先都是環(huán)境配置赏参。由于Flutter出自Google,所以有一定門(mén)檻浙芙,如果在公司內(nèi)安裝登刺,你還需要一個(gè)方便的代理切換工具, 比如:Proxifier。
安裝教程嗡呼,參照官網(wǎng):https://flutter.dev/docs/get-started/install
Flutter支持多種編輯器如:Android Studio , XCode纸俭。 但是既然作為支持跨雙端的開(kāi)發(fā),個(gè)人還是推薦使用VSCode南窗。
VSCode安裝完成后揍很,需要安裝Flutter插件,和Dart插件. 在擴(kuò)展窗口里万伤,搜索Flutter窒悔,和Dart,點(diǎn)擊“Install”即可敌买,非常方便简珠。
如果安裝不上去,記得開(kāi)啟下代理虹钮。
5.Hello World
作為一個(gè)偉大的程序員聋庵,第一行代碼總是從Hello World開(kāi)始。_
5.1 創(chuàng)建項(xiàng)目:
方法1:直接使用命令創(chuàng)建:
flutter create projectname
方法2:使用VSCode創(chuàng)建:
View -> Command Palette -> Flutter:New Project 即可
注意請(qǐng)先打開(kāi)代理芙粱,否則你的創(chuàng)建進(jìn)度祭玉,會(huì)一直被卡住。
5.2 項(xiàng)目結(jié)構(gòu)
將項(xiàng)目先拖入VSCode春畔,看下目錄結(jié)構(gòu)脱货。自動(dòng)創(chuàng)建完成的項(xiàng)目中,我們看到已經(jīng)自帶了Android律姨,IOS相關(guān)的運(yùn)行環(huán)境振峻。
入口主文件是main.dart. 可以打開(kāi)來(lái)先熟悉下,暫時(shí)不了解沒(méi)關(guān)系择份,后面再講扣孟。
還有一個(gè)重要的文件是pubspec.yaml ,是項(xiàng)目的配置文件缓淹,這個(gè)后續(xù)也會(huì)做修改哈打。
5.3 啟動(dòng)模擬器
點(diǎn)擊VSCode右下角的模擬器塔逃,啟動(dòng)模擬器。(VSCode會(huì)自動(dòng)找到Android環(huán)境料仗、IOS環(huán)境下的模擬器湾盗,以及真機(jī)環(huán)境)
5.4 啟動(dòng)項(xiàng)目APP
選中Main.dart, 點(diǎn)擊Debug-> Start Debugging, 項(xiàng)目就會(huì)啟動(dòng)調(diào)試立轧,并在模擬器里運(yùn)行格粪。
5.5 簡(jiǎn)化版的Hello World
講道理,F(xiàn)lutter一上來(lái)就用StatefulWidget做一個(gè)自增的Demo氛改,其實(shí)是對(duì)新手不太友好帐萎。 我還是喜歡循序漸進(jìn),先刪掉那些復(fù)雜的自增邏輯胜卤,我們基于StatelessWidget 只做一個(gè)最簡(jiǎn)單的靜態(tài)頁(yè)面顯示疆导。(什么是StatefulWidget 和StatelessWidget?后面會(huì)說(shuō))
main.dart
import'package:flutter/material.dart';voidmain()=>runApp(MyApp());classMyAppextendsStatelessWidget{// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Flutter Demo',home:MyHomePage(),);}}classMyHomePageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text("我是Title"),),body:Center(child:Text('Hello World',)));}}
在上面的代碼中葛躏,可以清楚看到澈段,最簡(jiǎn)單的頁(yè)面的層級(jí)關(guān)系:
MaterialApp -> MyHomePage -> Scaffold -> body ->Center -> Text
Scaffold是啥?他是Flutter的頁(yè)面腳手架舰攒,你可以當(dāng)HTML頁(yè)面一樣去理解败富,不同的是,他除了Body以外摩窃,還提供appBar頂部TitleBar兽叮、bottomNavigationBar底部導(dǎo)航欄等屬性。
顯示效果:
這是最簡(jiǎn)單的頁(yè)面猾愿,沒(méi)有交互鹦聪,只有顯示,但是實(shí)際業(yè)務(wù)場(chǎng)景中匪蟀,是不太可能都是這種頁(yè)面的椎麦,頁(yè)面上的數(shù)據(jù)一般都是來(lái)自接口返回宰僧,然后再在頁(yè)面上進(jìn)行動(dòng)態(tài)的渲染材彪。 此時(shí),就需要使用使用帶狀態(tài)的StatefulWidget了
5.6 給頁(yè)面加上狀態(tài)
給自己一個(gè)需求琴儿,按鈕點(diǎn)擊時(shí)段化,修改頁(yè)面上顯示的文字“Hello World” 變成“You Click Me”
import'package:flutter/material.dart';voidmain()=>runApp(MyApp());classMyAppextendsStatelessWidget{// This widget is the root of your application.@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Flutter Demo',home:MyHomePage(),);}}classMyHomePageextendsStatefulWidget{@overrideMyHomePageStatecreateState()=>MyHomePageState();}classMyHomePageStateextendsState<MyHomePage>{varmsg="Hello World";//msg默認(rèn)文字@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text("我是Title"),),body:Center(child:Column(children:<Widget>[Text(msg),//根據(jù)變量值,顯示文字FlatButton(color:Colors.blue,textColor:Colors.white,//點(diǎn)擊按鈕造成,修改msg的文字onPressed:(){setState((){this.msg="You Click ME";});},child:Text("Click ME",style:TextStyle(fontSize:20.0),),)])));}}
執(zhí)行效果:
上面最關(guān)鍵的一段代碼就是這個(gè):
onPressed:(){setState((){this.msg="You Click ME";});},
相信寫(xiě)過(guò)小程序的同學(xué)显熏,對(duì)這個(gè) setState 還是很眼熟的_
5.7 小結(jié)一下
StatelessWidget:無(wú)狀態(tài)變更,UI靜態(tài)固化的Widget晒屎, 頁(yè)面渲染性能更高喘蟆。
StatefulWidget:因狀態(tài)變更可以導(dǎo)致UI變更的的Widget缓升,涉及到數(shù)據(jù)渲染場(chǎng)景申鱼,都使用StatefulWidget珊擂。
為啥要分兩個(gè)蜓席? StatelessWidget擁有的功能捡偏,StatefulWidget都有了岸匀恕顿膨?
答案只有一個(gè):性能翅雏、性能揣云、性能
在StatefulWidget里棘脐,因?yàn)橐S護(hù)狀態(tài)斜筐,他的生命周期比StatelessWidget更復(fù)雜,每次執(zhí)行setState蛀缝,都會(huì)觸發(fā)
window.scheduleFrame() 導(dǎo)致整個(gè)頁(yè)面的widget被刷新顷链,性能就會(huì)降低。
使用過(guò)小程序的同學(xué)在這點(diǎn)上應(yīng)該有體會(huì)屈梁,在小程序的官方文檔中蕴潦,會(huì)強(qiáng)烈建議減少setData的使用頻率,以避免性能的下降俘闯。 只不過(guò)flutter更是激進(jìn)潭苞,推出了StatelessWidget,并直接在該Widget里砍掉了setState的使用真朗。
頁(yè)面結(jié)構(gòu)關(guān)系如下:
6.路由
實(shí)際的項(xiàng)目此疹,是有多個(gè)不同的頁(yè)面的,頁(yè)面之間的跳轉(zhuǎn)遮婶,就要用到路由了蝗碎。 我們?cè)黾右粋€(gè)list頁(yè)面,點(diǎn)擊Home頁(yè)的“Click Me”按鈕旗扑,跳轉(zhuǎn)到列表頁(yè)list蹦骑。
6.1 單個(gè)頁(yè)面的跳轉(zhuǎn)
增加list.dart
import'package:flutter/material.dart';classListPageextendsStatelessWidget{@override? Widgetbuild(BuildContext context){//定義列表widget的listList<Widget>list=<Widget>[];//Demo數(shù)據(jù)定義vardata=[{"id":1,"title":"測(cè)試數(shù)據(jù)AAA","subtitle":"ASDFASDFASDF"},{"id":2,"title":"測(cè)試數(shù)據(jù)bbb","subtitle":"ASDFASDFASDF"},{"id":3,"title":"測(cè)試數(shù)據(jù)ccc","subtitle":"ASDFASDFASDF"},{"id":4,"title":"測(cè)試數(shù)據(jù)eee","subtitle":"ASDFASDFASDF"},];//根據(jù)Demo數(shù)據(jù),構(gòu)造列表ListTile組件listfor(varitem in data){print(item["title"]);list.add(ListTile(title:Text(item["title"],style:TextStyle(fontSize:18.0)),subtitle:Text(item["subtitle"]),leading:Icon(Icons.fastfood,color:Colors.orange),trailing:Icon(Icons.keyboard_arrow_right)));}//返回整個(gè)頁(yè)面returnScaffold(appBar:AppBar(title:Text("List Page"),),body:Center(child:ListView(children:list,)),);}}
在main.dart增加list頁(yè)面的引入
import'list.dart';
修改Home頁(yè)的按鈕事件臀防,增加Navigator.push跳轉(zhuǎn)
FlatButton(color:Colors.blue,textColor:Colors.white,onPressed:(){Navigator.push(context,MaterialPageRoute(builder:(context){returnListPage();}));},child:Text("Click ME",style:TextStyle(fontSize:20.0)),)
核心方法就是:Navigator.push(context,MaterialPageRoute)
跳轉(zhuǎn)示例:
6.2 更多頁(yè)面跳轉(zhuǎn)使用路由表
在MaterialApp中眠菇,有一個(gè)屬性是routes,我們可以對(duì)路由進(jìn)行命名袱衷,這樣跳轉(zhuǎn)的時(shí)候捎废,只需要使用對(duì)應(yīng)的路由名字即可,如:Navigator.pushNamed(context, RouterName)致燥。點(diǎn)擊兩個(gè)不同的按鈕登疗,分別跳轉(zhuǎn)到ListPage,和Page2去。
Main.dart修改一下如下:
import'package:flutter/material.dart';import'list.dart';import'page2.dart';voidmain()=>runApp(MyApp());classMyAppextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){returnMaterialApp(title:'Flutter Demo',//路由表定義routes:{"ListPage":(context)=>ListPage(),"Page2":(context)=>Page2(),},home:MyHomePage(),);}}classMyHomePageextendsStatefulWidget{@overrideMyHomePageStatecreateState()=>MyHomePageState();}classMyHomePageStateextendsState<MyHomePage>{@overrideWidgetbuild(BuildContext context){returnScaffold(appBar:AppBar(title:Text("我是Title"),),body:Center(child:Column(children:<Widget>[RaisedButton(child:Text("Clikc to ListPage"),onPressed:(){//根據(jù)命名路由做跳轉(zhuǎn)Navigator.pushNamed(context,"ListPage");},),RaisedButton(child:Text("Click to Page2"),onPressed:(){//根據(jù)命名路由做跳轉(zhuǎn)Navigator.pushNamed(context,"Page2");},)])));}}
示例:
當(dāng)我們有了路由以后辐益,就可以開(kāi)始在一個(gè)項(xiàng)目里用不同的頁(yè)面断傲,去學(xué)習(xí)不同的功能了。
6.3 路由傳參
列表頁(yè)跳轉(zhuǎn)到詳情頁(yè)智政,需要路由傳參艳悔,這個(gè)在flutter體系里,又是怎么做的呢女仰?
首先猜年,在main.dart里,增加詳情頁(yè)DedailPage的路由配置
//路由表定義routes:{"ListPage":(context)=>ListPage(),"Page2":(context)=>Page2(),"DetailPage":(context)=>DetailPage(),//增加詳情頁(yè)的路由配置},
并修改ListPage里L(fēng)istTile的點(diǎn)擊事件疾忍,增加路由跳轉(zhuǎn)傳參乔外,這里是將整個(gè)item數(shù)據(jù)對(duì)象傳遞
ListTile(title:Text(item["title"],style:TextStyle(fontSize:18.0)),subtitle:Text(item["subtitle"]),leading:Icon(Icons.fastfood,color:Colors.orange),trailing:Icon(Icons.keyboard_arrow_right),onTap:(){//點(diǎn)擊的時(shí)候,進(jìn)行路由跳轉(zhuǎn)傳參Navigator.pushNamed(context,"DetailPage",arguments:item);},)
詳情頁(yè)DetailPage里一罩,獲取傳參并顯示
import'package:flutter/material.dart';classDetailPageextendsStatelessWidget{@overrideWidgetbuild(BuildContext context){//獲取路由傳參finalMap args=ModalRoute.of(context).settings.arguments;returnScaffold(appBar:AppBar(title:Text("Detail Page"),),body:newColumn(children:<Widget>[Text("我是Detail頁(yè)面"),Text("id:${args['id']}"),Text("id:${args['title']}"),Text("id:${args['subtitle']}")],));}}
Demo效果:
7.widget
Flutter提供了很多默認(rèn)的組件杨幼,而每個(gè)組件的都繼承自widget 。 在Flutter眼里:一切都是widget聂渊。 這句看起來(lái)是不是很熟悉差购? 還記得在webpack里,一切都是module嗎汉嗽? 類(lèi)似的還有java的一切都是對(duì)象欲逃。貌似任何一個(gè)技術(shù),最后都是用哲學(xué)作為指導(dǎo)思想饼暑。
widget稳析,作為可視化的UI組件,包含了顯示UI弓叛、功能交互兩部分彰居。大的widget,也可以由多個(gè)小的widget組合而成撰筷。
常用的widget組件:
7.1 Text
Demo:
Text("Hello world",style:TextStyle(fontSize:50,fontWeight:FontWeight.bold,color:Color(0xFF0000ff))),
Text的樣式陈惰,來(lái)自另一個(gè)widget:TextStyle。 而TextStyle里的color毕籽,又是另一個(gè)widget Color的實(shí)例抬闯。
如果用flutter的縮進(jìn)的方法,看起來(lái)確實(shí)有點(diǎn)丑陋影钉,習(xí)慣寫(xiě)CSS的前端同學(xué)画髓,可以看看下面的風(fēng)格:
Text("Hello world",style:TextStyle(fontSize:50,fontWeight:FontWeight.bold,color:Color(0xFF0000ff)))
寫(xiě)成一行掘剪,是不是就順眼多了平委?這算前端惡習(xí)嗎?_
7.2 Button
對(duì)于flutter來(lái)說(shuō)夺谁,Button就提供了很多種廉赔,我們來(lái)看看他們的區(qū)別:
RaisedButton: 凸起的按鈕
FlatButton:扁平化按鈕
OutlineButton:帶邊框按鈕
IconButton:帶圖標(biāo)按鈕
按鈕測(cè)試頁(yè)dart:
import'package:flutter/material.dart';classButtonPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text("Button Page"),),body:Column(children:<Widget>[RaisedButton(child:Text("我是 RaiseButton"),onPressed:(){},),FlatButton(child:Text("我是 FlatButton"),color:Colors.blue,onPressed:(){},),OutlineButton(child:Text("我是 OutlineButton"),textColor:Colors.blue,onPressed:(){},),IconButton(icon:Icon(Icons.add),onPressed:(){},)]));}}
Demo:
項(xiàng)目中要用哪個(gè)肉微,就各取所需吧~
7.3 Container
Container是非常常用的一個(gè)widget,他一般是用作一個(gè)容器蜡塌。我們先來(lái)看看他的基礎(chǔ)屬性碉纳,順便可以想想他像HTML里的啥?
基礎(chǔ)屬性:width馏艾,height劳曹,color,child
body:Center(child:Container(color:Colors.blue,width:200,height:200,child:Text("Hello Container ",style:TextStyle(fontSize:20,color:Colors.white)),))
Padding
我們也可以不設(shè)置寬高琅摩,用padding在內(nèi)部撐開(kāi)增加留白:
Container(color:Colors.blue,padding:EdgeInsets.all(30),child:Text("Hello Container ",style:TextStyle(fontSize:20,color:Colors.white)),)
Margin
我們還可以使用margin铁孵,在容器的外部撐開(kāi)增加偏移量,
Container(color:Colors.blue,padding:EdgeInsets.all(30),margin:EdgeInsets.only(left:150,top:0,right:0,bottom:0),child:Text("Hello Container ",style:TextStyle(fontSize:20,color:Colors.white)),)
Transform
我們還可以給這個(gè)矩形房资,使用tansform做一些變化蜕劝,比如,旋轉(zhuǎn)一個(gè)角度
Container(color:Colors.blue,padding:EdgeInsets.all(30),child:Text("Hello Container ",style:TextStyle(fontSize:20,color:Colors.white)),transform:Matrix4.rotationZ(0.5))
看到這里轰异,好多前端同學(xué)要說(shuō)了岖沛,好熟悉啊。 對(duì)搭独,他就是很像Html里的一個(gè)東西:DIV婴削,你確實(shí)可以對(duì)應(yīng)的去加強(qiáng)理解。
7.4 Image
網(wǎng)絡(luò)圖片加載
使用NetworkImage牙肝,可以做網(wǎng)絡(luò)圖片的加載:
child:Image(? ? ? ? ? image: NetworkImage("https://mat1.gtimg.com/pingjs/ext2020/qqindex2018/dist/img/qq_logo_2x.png"),? ? ? ? ? width: 200.0,? ? ? ? )
本地圖片加載
加載本地圖片馆蠕,就稍微復(fù)雜一些,首先要把圖片的路徑配置惊奇,加入到之前說(shuō)過(guò)的pubspec.yaml配置文件里去:
加載本地圖片時(shí)使用AssetImage:
child:Image(? ? ? ? ? ? ? image: AssetImage("assets/images/logo.png"),? ? ? ? ? ? ? ? width: 200.0,? ? ? ? ? ? )
也可以使用簡(jiǎn)寫(xiě):
Image.asset("assets/images/logo.png",width:200.0)
flutter提供的組件很多互躬,這里就不一一舉例說(shuō)明,有興趣的還是建議大家去看API:https://api.flutter.dev/
8.布局
我們已經(jīng)了解了這么多組件颂郎,那么怎么繪制一個(gè)完整的頁(yè)面呢吼渡? 這就到了頁(yè)面布局的部分了。
8.1 Row & Column & Center 行列軸布局
字面意義也很好理解乓序,行布局寺酪、列布局、居中布局替劈,這些布局對(duì)于Flutter來(lái)說(shuō)寄雀,也都是一個(gè)個(gè)的widget。
區(qū)別在于陨献,row盒犹、column 是有多個(gè)children的widget, 而Center是只有 1個(gè)child的 widget。
Row(children:[])Column(children:[])Center(child:Text("Hello"))
8.2 Align 角定位布局
我們常常在Container里急膀,需要顯示的內(nèi)容在左上角沮协,左下角,右上角卓嫂,右下角慷暂。 在html時(shí)代,使用CSS可以很容易的實(shí)現(xiàn)晨雳,但是flutter里行瑞,必須依賴(lài)Align 這個(gè)定位的Widget
右下角定位示例:
child:Container(color:Colors.blue,width:300,height:200,child:Align(alignment:Alignment.bottomRight,child:Text("Hello Align ",style:TextStyle(fontSize:20,color:Colors.white)),))
顯示效果:
Alignment提供了多種定位供選擇,還算是很貼心的餐禁。
8.3 Stack & Positioned 絕對(duì)定位
當(dāng)然還有絕對(duì)定位的需求蘑辑,這在css里,使用position:absolute就搞定了坠宴,但是在flutter里洋魂,需要借助stack+ positioned兩個(gè)widget一起組合使用。
Stack: 支持元素堆疊
Positioned:支持絕對(duì)定位
child:Stack(children:<Widget>[Image.network("https://ossweb-img.qq.com/upload/adw/image/20191022/627bdf586e0de8a29d5a48b86700a790.jpeg"),Positioned(top:20,right:10,child:Image.asset("assets/images/logo.png",width:200.0))],)
8.4 Flex & Expanded 流式布局
Flex流式布局作為前端同學(xué)都熟悉喜鼓,之前講過(guò)的Row副砍,Column,其實(shí)都是繼承自Flex庄岖,也屬于流式布局豁翎。
如果軸向不確定,使用Flex隅忿,通過(guò)修改direction的值設(shè)定軸向
如果軸向已確定心剥,使用Row,Column背桐,布局更簡(jiǎn)潔优烧,更有語(yǔ)義化
Flex測(cè)試頁(yè):
classFlexPageextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text("Flex Page"),),body:Flex(direction:Axis.horizontal,children:<Widget>[Container(width:30,height:100,color:Colors.blue,),Expanded(flex:1,child:Container(height:100.0,color:Colors.red,),),Expanded(flex:1,child:Container(height:100.0,color:Colors.green,),),],),);}}
示例中,軸向橫向排列链峭,最左邊一個(gè)固定寬度的Container畦娄,右邊兩個(gè)Expanded,各自占剩下的寬度的一半弊仪。
9.動(dòng)畫(huà)
Flutter既然說(shuō)了熙卡,一切都是Widget,包括動(dòng)畫(huà)實(shí)現(xiàn)励饵,也是一個(gè)Widget驳癌。 我們還是看一個(gè)示例
9.1 簡(jiǎn)單動(dòng)畫(huà):淡入淡出:
使用flutter提供的現(xiàn)成的Widget:
import'package:flutter/material.dart';classAnimatePageextendsStatefulWidget{_AnimatePagecreateState()=>_AnimatePage();}class_AnimatePageextendsState<AnimatePage>{bool _visible=true;@overrideWidgetbuild(BuildContext context){returnScaffold(appBar:AppBar(title:Text("Animate Page"),),body:Center(child:Column(children:<Widget>[AnimatedOpacity(opacity:_visible?1.0:0.0,duration:Duration(milliseconds:1000),child:Image.asset("assets/images/logo.png"),),RaisedButton(child:Text("顯示隱藏"),onPressed:(){setState((){_visible=!_visible;});},),],),));}}
其中的AnimatedOpacity就是動(dòng)畫(huà)透明度變化的的Widget,而被透明度控制變化的Image則是AnimatedOpacity的子元素役听。這個(gè)和以往前端寫(xiě)動(dòng)畫(huà)的方式颓鲜,就完全不一樣了表窘,需要改變一下思維方式。
Demo效果
9.2 復(fù)雜一些的動(dòng)畫(huà):放大縮小
當(dāng)寫(xiě)復(fù)雜一些動(dòng)畫(huà)的時(shí)候灾杰,沒(méi)有對(duì)應(yīng)的widget組件蚊丐,就需要自己使用Animation熙参,和AnimationController艳吠,以及Tween來(lái)組合。
Animation: 保存動(dòng)畫(huà)的值和狀態(tài)
AnimationController: 控制動(dòng)畫(huà)孽椰,包含:?jiǎn)?dòng)forward()昭娩、停止stop()、反向播放reverse()等方法
Tween: 提供begin黍匾,end作為動(dòng)畫(huà)變化的取值范圍
Curve:設(shè)置動(dòng)畫(huà)使用曲線(xiàn)變化栏渺,如非勻速動(dòng)畫(huà),先加速锐涯,后減速等的設(shè)定磕诊。
動(dòng)畫(huà)示例:
classAnimatePage2extendsStatefulWidget{_AnimatePagecreateState()=>_AnimatePage();}class_AnimatePageextendsState<AnimatePage2>withSingleTickerProviderStateMixin{Animation<double>animation;AnimationController controller;initState(){super.initState();controller=AnimationController(duration:Duration(seconds:3),vsync:this);//使用彈性曲線(xiàn),數(shù)據(jù)變化從0到300animation=CurvedAnimation(parent:controller,curve:Curves.bounceIn);animation=Tween(begin:0.0,end:300.0).animate(animation)..addListener((){setState((){});});//啟動(dòng)動(dòng)畫(huà)(正向執(zhí)行)controller.forward();}@overrideWidgetbuild(BuildContext context){returnScaffold(appBar:AppBar(title:Text("Animate Page"),),body:Center(child:Image.asset("assets/images/logo.png",width:animation.value,height:animation.value),));}dispose(){//路由銷(xiāo)毀時(shí)需要釋放動(dòng)畫(huà)資源controller.dispose();super.dispose();}}
很重要的一點(diǎn)纹腌,在路由銷(xiāo)毀的時(shí)候霎终,需要釋放動(dòng)畫(huà)資源,否則容易導(dǎo)致內(nèi)存泄漏升薯。
顯示Demo:
image
10.http請(qǐng)求
做業(yè)務(wù)邏輯莱褒,總離不開(kāi)http請(qǐng)求,接下來(lái)涎劈,就來(lái)看下flutter的http請(qǐng)求是如何做的广凸。
10.1 HttpClient
httpClient在 dart:io庫(kù)中,不需要引入第三方庫(kù)就可以使用蛛枚,示例代碼如下:
使用示例
import'dart:convert';import'dart:io';Future_getByHttpClient()async{//接口地址consturl="https://www.demo.com/api";//定義httpClientHttpClient client=newHttpClient();//定義requestHttpClientRequest request=awaitclient.getUrl(Uri.parse(url));//定義reponseHttpClientResponse response=awaitrequest.close();//respinse返回的數(shù)據(jù)谅海,是字符串String responseBody=awaitresponse.transform(utf8.decoder).join();//關(guān)閉httpClientclient.close();//字符串需要轉(zhuǎn)化為JSONvarjson=jsonDecode(responseBody);returnjson;}
總的看起來(lái),代碼還是挺繁瑣的蹦浦,使用起來(lái)并不方便胁赢。
10.2 http
這是Dart.dev提供的第三方類(lèi)庫(kù),地址:https://pub.dev/packages/http
需要先在pubspec.yaml里添加類(lèi)庫(kù)應(yīng)用
dependencies:flutter:sdk:flutterjson_annotation:^2.0.0http:^0.12.0+2
使用示例:
Future_getByDartHttp()async{// 接口地址consturl="https://www.demo.com/api";//獲取接口的返回值finalresponse=awaithttp.get(url);//接口的返回值轉(zhuǎn)化為JSONvarjson=jsonDecode(response.body);returnjson;}
這種寫(xiě)法白筹,比上面的httpClient簡(jiǎn)潔了許多智末。
Dio
國(guó)內(nèi)使用最廣泛的,還是flutterchina在github上提供的Dio第三方庫(kù)徒河,目前Star達(dá)到了5800多個(gè)系馆。
官網(wǎng)地址:https://github.com/flutterchina/dio
使用Dio,因?yàn)槭堑谌綆?kù)顽照,所以同樣要先在 pubspec.yaml 添加第三方庫(kù)引用由蘑。
dependencies:flutter:sdk:flutterjson_annotation:^2.0.0dio:2.1.16
使用示例:
import'package:dio/dio.dart';Future_getByDio()async{// 接口地址consturl="https://www.demo.com/api";//定義 Dio實(shí)例Dio dio=newDio();//獲取dio返回的ResponseResponse response=awaitdio.get(url);//返回值轉(zhuǎn)化為JSONvarjson=jsonDecode(response.data);returnjson;}
接口調(diào)用也是比httpclient簡(jiǎn)單很多闽寡,可能由于fluterchina在他的官方教程里,極力推薦這個(gè)dio庫(kù)尼酿,所以目前這個(gè)第三方庫(kù)的使用情況最為廣泛爷狈。和Dart.dev的http不同的是,他需要new一個(gè)Dio的實(shí)例裳擎,在創(chuàng)建實(shí)例的時(shí)候涎永,還可以傳入更多的擴(kuò)展配置參數(shù)。
BaseOptions options=newBaseOptions(baseUrl:"https://www.xx.com/api",connectTimeout:5000,receiveTimeout:3000,);Dio dio=newDio(options);
11.吐吐槽
學(xué)習(xí)Flutter的過(guò)程中鹿响,其實(shí)還是有很多坎坷和需要吐槽的地方羡微。
11.1 墻
因?yàn)橛袎υ冢栽谂渲胒lutter惶我,或者下載flutter插件和第三方庫(kù)的時(shí)候妈倔,需要墻內(nèi)外來(lái)回切換。
11.2 組件過(guò)度設(shè)計(jì)
提供的各種widget組件很多绸贡,但是真正核心的組件盯蝴、常用的組件,也就哪些听怕。 比如Flex 和column捧挺、row的關(guān)系,比如叉跛,Tween 與IntTween松忍,ColorTween,SizeTween等20多個(gè)Tween子類(lèi)之間的關(guān)系筷厘,你需要花很大的精力鸣峭,去看每個(gè)具體子類(lèi)的實(shí)現(xiàn)差別。
11.3 嵌套太多不適應(yīng)
因?yàn)榍短讓蛹?jí)很多酥艳,而且布局摊溶、動(dòng)畫(huà)、功能都在一起充石,第一次上手Flutter和Dart莫换,這種嵌套關(guān)系讓人很暈菜,這個(gè)只能去慢慢克服骤铃。 另外拉岁,多開(kāi)發(fā)自定義的組件,可以讓嵌套關(guān)系看起來(lái)清晰一些惰爬。
11.4 布局修改會(huì)導(dǎo)致嵌套關(guān)系修改
前端的html+css分離世界里喊暖,不改變嵌套關(guān)系,修改CSS就可以調(diào)整布局撕瞧。 但是在Flutter里因?yàn)椴季忠彩乔短钻P(guān)系陵叽,這就導(dǎo)致必須去改變嵌套關(guān)系狞尔。 要讓嵌套更簡(jiǎn)單變動(dòng)影響更小,頁(yè)面拆分成子組件變得尤為重要巩掺。
11.5 Dart語(yǔ)言升級(jí)
沒(méi)錯(cuò)偏序,語(yǔ)言升級(jí)也會(huì)導(dǎo)致學(xué)習(xí)的困擾,外面的資料新舊都有胖替,比如有些是 new Text() ,有些直接是Text() 研儒,新手上路會(huì)很暈菜。 其實(shí)這都是Dart語(yǔ)言升級(jí)導(dǎo)致的刊殉,記住Dart升級(jí)2.X以后殉摔,都不使用new了州胳。感興趣的可以自己去看下Dart的升級(jí)變更說(shuō)明记焊。
11.6 不能熱更新
年中的時(shí)候,Google官方宣布flutter暫不官方支持熱更新栓撞,但是閑魚(yú)團(tuán)隊(duì)已經(jīng)有了自己的熱更新方案遍膜。 關(guān)于熱更新,只能靜觀(guān)其變了瓤湘。 性能瓢颅、開(kāi)發(fā)效率、熱更新弛说,總是要有取舍的挽懦。即使是閑魚(yú)團(tuán)隊(duì),熱更新也是付出了一點(diǎn)點(diǎn)性能下降的代價(jià)的木人,這是你選擇flutter的初衷嗎信柿?還是那句話(huà):權(quán)衡得失。
12.結(jié)語(yǔ)
隨著 9 月谷歌發(fā)布 Flutter1.9 以及flutter for web醒第,F(xiàn)lutter的組件化思路渔嚷,使得一份代碼跨三端變成可能,相信Flutter的未來(lái)會(huì)更加廣闊稠曼。
這不是一篇教程形病,只是在學(xué)習(xí)Flutter過(guò)程中的一點(diǎn)體驗(yàn)和經(jīng)歷,也因?yàn)闀r(shí)間關(guān)系霞幅,研究并不深入漠吻,如有疏漏,還請(qǐng)不吝賜教司恳。
學(xué)習(xí)分享途乃,共勉
題外話(huà),畢竟我在三星小米工作多年抵赢,深知技術(shù)改革和創(chuàng)新的方向欺劳,F(xiàn)lutter作為跨平臺(tái)開(kāi)發(fā)技術(shù)唧取、Flutter以其美觀(guān)、快速划提、高效枫弟、開(kāi)放等優(yōu)勢(shì)迅速俘獲人心,但很多FLutter興趣愛(ài)好者進(jìn)階學(xué)習(xí)確實(shí)資料鹏往,今天我把我搜集和整理的這份學(xué)習(xí)資料分享給有需要的人淡诗,若有關(guān)Flutter學(xué)習(xí)進(jìn)階可以與我在Flutter跨平臺(tái)開(kāi)發(fā)終極之選交流群一起討論交流。下載地址:https://shimo.im/docs/yTD3t8Pjq3XJtGv8
作者:Alvin老師
鏈接:http://www.reibang.com/p/ca0ef3eb32b1
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有伊履。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)韩容,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。