轻松 Flutter 入门,秒变大前端
本文作者:dickma,騰訊 IEG 前端開發(fā)工程師
本文不是Flutter的教程,只是對 Flutter 的技術(shù)特性,做了一些略全面的入門級的介紹,如果你聽說過Flutter,想去了解他,但是又不想去翻厚厚的API,那么本文就是為你準(zhǔn)備的。
隨著純客戶端到Hybrid技術(shù),到RN&Weex,再到如今的Flutter技術(shù),客戶端實現(xiàn)技術(shù)不斷前進。在之前的一個APP項目中,因為歷史原因當(dāng)時選擇了weex,隨著使用的不斷深入,我們逐漸發(fā)現(xiàn)了weex的渲染性能問題已經(jīng)成為一個隱患和瓶頸。隨著Flutter技術(shù)的不斷成熟和流行,Flutter的良好的跨平臺性和高性能優(yōu)點,不斷吸引著我們,他是否可以幫助我們解決這些問題呢?因此,才有本文這篇技術(shù)嘗試。
(本文包含以下內(nèi)容,閱讀完需要約18分鐘)
1.Flutter是啥玩意兒?
2.移動端跨平臺技術(shù)對比
2.1 H5+原生APP
2.2 RN&Weex
2.3 Flutter
3.Dart語言
4.環(huán)境配置
5.Hello World
5.1 創(chuàng)建項目
5.2 項目結(jié)構(gòu)
5.3 啟動模擬器
5.4 啟動項目APP
5.5 簡化版的Hello World
5.6 給頁面加上狀態(tài)
5.7 小結(jié)一下
6.路由
6.1 單個頁面的跳轉(zhuǎn)
6.2 更多頁面跳轉(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 絕對定位
8.4 Flex & Expanded 流式布局
9.動畫
9.1 簡單動畫:淡入淡出
9.2 復(fù)雜一些的動畫:放大縮小
10.http請求
10.1 HttpClient
10.2 http
10.3 Dio
11.吐吐槽
11.1 墻
11.2 組件過度設(shè)計
11.3 嵌套太多不適應(yīng)
11.4 布局修改會導(dǎo)致嵌套關(guān)系修改
11.5 Dart語言升級
11.6 不能熱更新
12.結(jié)語
1.Flutter是啥玩意兒?
Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構(gòu)建高質(zhì)量的原生用戶界面。
具有跨平臺開發(fā)特性,支持IOS、Android、Web三端。
熱重載特性大大提高了開發(fā)效率
自繪UI引擎和編譯成原生代碼的方式,使得系統(tǒng)的運行時的高性能成為了可能
使用Dart語言,目前已經(jīng)支持同時編譯成Web端代碼,
到底值不值得跟進Flutter技術(shù)呢?還是看下Flutter,Weex,ReactNative的搜索指數(shù)對比,大概就知道這個行業(yè)趨勢了。
藍色是Flutter,可以看出上升勢頭非常強勁。苦逼的前端就是這樣,你不跟潮流,潮流就會把你拋棄。
2.移動端跨平臺技術(shù)對比
為啥會有Flutter這種東西? 他的原理是什么?他是怎么做到高性能的?要明白這些問題,我們不得不從幾種移動端跨平臺技術(shù)的對比講起。
2.1 H5+原生APP
圖片來源于網(wǎng)絡(luò)技術(shù)門檻最低,接入速度最快,熱更新最方便的,自然就是H5方式。APP中提供一個Webview使用H5頁面的Http直連。APP和H5可以相互獨立開發(fā),JS使用Bridge與原生進行數(shù)據(jù)通信,顯示界面依賴Webview的瀏覽器渲染。但是帶來的問題也很明顯,因為是需要遠程直連,那么初次打開H5頁面,會有瞬間的白屏,并且Webview本身會有至少幾十M的內(nèi)存消耗。
當(dāng)然,作為前端開發(fā)人員,在H5方式可以使用SPA單頁面、懶加載、離線H5等各種前端優(yōu)化手段進行性能優(yōu)化,以使得H5的表現(xiàn)更接近原生。但是首次的瞬間白屏和內(nèi)存,Bridge的通信效率低下,始終是被技術(shù)框架給局限住了。
2.2 RN&Weex
圖片來源于網(wǎng)絡(luò)由于H5的那些弊端,愛折騰的前端工程師,祭出了RN、Weex兩個大殺器, 使用原生去解析RN、Weex的顯示配置,顯示層、邏輯層都直接與原生數(shù)據(jù)通信。因為拋棄了瀏覽器,自然渲染性能、執(zhí)行性能都提升了一大截。
但是,每次遇到顯示的變更,JS都還會通過Bridge和原生轉(zhuǎn)一道再做渲染的調(diào)整,所以Bridge就最后成為了性能的瓶頸。在實際項目中,特別是做一些大量復(fù)雜動畫處理的時候,由于渲染部分需要頻繁通信,性能問題變得尤為突出。有興趣的同學(xué)可以去看看BindingX,里面有關(guān)于動畫中數(shù)據(jù)通信效率低下導(dǎo)致動畫幀率低下的詳細(xì)說明。
2.3 Flutter
圖片來源于網(wǎng)絡(luò)不得不佩服Google開發(fā)人員的想象力,為了達到極致性能,Flutter更前進了一步,Flutter代碼編譯完成以后,直接就是原生代碼,并且使用自繪UI引擎原生方式做渲染。Flutter依賴一個Skia 2D圖形化引擎。Skia也是Android平臺和Chrome的底層渲染引擎,所以性能方面完全不用擔(dān)心。因為使用Dart做AOT編譯成原生,自然也比使用解釋性的JS在V8引擎中執(zhí)行性能更快,并且因為去掉Bridge,沒有了繁瑣的數(shù)據(jù)通信和交互,性能就更前進了一步。
3.Dart語言
學(xué)習(xí)Flutter,得先了解Dart。Dart語言曾經(jīng)雄心勃勃的要替換Javascript, 但是發(fā)布的時機正好遇到JS的飛速發(fā)展,于是就逐漸沉寂,直到配合Flutter的發(fā)布,才又重新煥發(fā)了生機。
在最近2019年9月的一次Google開發(fā)者大會中,伴隨著Flutter1.9的發(fā)布,目前的Dart也同時更新到了2.5版本, 提供了機器學(xué)習(xí)和對C跨平臺調(diào)用的能力。總體來說,Dart語法,對于前端同學(xué),上手還是很容易的,風(fēng)格很像。
關(guān)于Dart語法,請移步傳送門:https://dart.dev/samples
4.環(huán)境配置
無論學(xué)什么新語言,首先都是環(huán)境配置。由于Flutter出自Google,所以,,如果在公司內(nèi)安裝,你還需要一個方便的代理切換工具, 比如:Proxifier 。
安裝教程,參照官網(wǎng):https://flutter.dev/docs/get-started/install
Flutter支持多種編輯器如:Android Studio , XCode。但是既然作為支持跨雙端的開發(fā),個人還是推薦使用 VSCode。
VSCode安裝完成后,需要安裝Flutter插件,和Dart插件. 在擴展窗口里,搜索Flutter,和Dart,點擊“Install”即可,非常方便。
如果安裝不上去,記得開啟下代理,代理配置如下:
5.Hello World
作為一個偉大的程序員,第一行代碼總是從Hello World開始。^_^
5.1 創(chuàng)建項目:
方法1:直接使用命令創(chuàng)建:
flutter?create?projectname方法2:使用VSCode創(chuàng)建:
View -> Command Palette -> Flutter:New Project 即可
注意請先打開代理,否則你的創(chuàng)建進度,會一直被卡住。
5.2 項目結(jié)構(gòu)
將項目先拖入VSCode,看下目錄結(jié)構(gòu)。自動創(chuàng)建完成的項目中,我們看到已經(jīng)自帶了Android,IOS相關(guān)的運行環(huán)境。
入口主文件是main.dart. 可以打開來先熟悉下,暫時不了解沒關(guān)系,后面再講。
還有一個重要的文件是pubspec.yaml ,是項目的配置文件,這個后續(xù)也會做修改。
5.3 啟動模擬器
點擊VSCode右下角的模擬器,啟動模擬器。(VSCode會自動找到Android環(huán)境、IOS環(huán)境下的模擬器,以及真機環(huán)境)
5.4 啟動項目APP
選中Main.dart, 點擊Debug-> Start Debugging , 項目就會啟動調(diào)試,并在模擬器里運行。
5.5 簡化版的Hello World
講道理,Flutter一上來就用StatefulWidget做一個自增的Demo,其實是對新手不太友好。我還是喜歡循序漸進,先刪掉那些復(fù)雜的自增邏輯,我們基于StatelessWidget 只做一個最簡單的靜態(tài)頁面顯示。(什么是StatefulWidget 和StatelessWidget?后面會說)
main.dart
import?'package:flutter/material.dart';void?main()?=>?runApp(MyApp());class?MyApp?extends?StatelessWidget?{//?This?widget?is?the?root?of?your?application.@overrideWidget?build(BuildContext?context)?{return?MaterialApp(title:?'Flutter?Demo',home:?MyHomePage(),);} }class?MyHomePage?extends?StatelessWidget{@overrideWidget?build(BuildContext?context)?{return?Scaffold(appBar:?AppBar(title:?Text("我是Title"),),body:?Center(child:?Text('Hello?World',)));}?? }在上面的代碼中,可以清楚看到,最簡單的頁面的層級關(guān)系:
MaterialApp -> MyHomePage -> Scaffold -> body -> Center -> Text
Scaffold是啥?他是Flutter的頁面腳手架,你可以當(dāng)HTML頁面一樣去理解,不同的是,他除了Body以外,還提供appBar頂部TitleBar、bottomNavigationBar底部導(dǎo)航欄等屬性。
顯示效果:
這是最簡單的頁面,沒有交互,只有顯示,但是實際業(yè)務(wù)場景中,是不太可能都是這種頁面的,頁面上的數(shù)據(jù)一般都是來自接口返回,然后再在頁面上進行動態(tài)的渲染。此時,就需要使用使用帶狀態(tài)的StatefulWidget了
5.6 給頁面加上狀態(tài)
給自己一個需求,按鈕點擊時,修改頁面上顯示的文字“Hello World” 變成“You Click Me”
import?'package:flutter/material.dart';void?main()?=>?runApp(MyApp());class?MyApp?extends?StatelessWidget?{//?This?widget?is?the?root?of?your?application.@overrideWidget?build(BuildContext?context)?{return?MaterialApp(title:?'Flutter?Demo',home:?MyHomePage(),);} }class?MyHomePage?extends?StatefulWidget{@overrideMyHomePageState?createState()?=>?MyHomePageState(); }class?MyHomePageState?extends?State<MyHomePage>{var?msg="Hello?World";?//msg默認(rèn)文字@overrideWidget?build(BuildContext?context)?{return?Scaffold(appBar:?AppBar(title:?Text("我是Title"),),body:?Center(child:Column(children:<Widget>[Text(msg),?//根據(jù)變量值,顯示文字FlatButton(color:?Colors.blue,textColor:?Colors.white,//點擊按鈕,修改msg的文字onPressed:?()?{setState(()?{this.msg="You?Click?ME";});},child:?Text("Click?ME",style:?TextStyle(fontSize:?20.0),),)])));}??}執(zhí)行效果:
上面最關(guān)鍵的一段代碼就是這個:
?onPressed:?()?{setState(()?{this.msg="You?Click?ME";});},相信寫過小程序的同學(xué),對這個 setState 還是很眼熟的 ^_^
5.7 小結(jié)一下
StatelessWidget:無狀態(tài)變更,UI靜態(tài)固化的Widget, 頁面渲染性能更高。
StatefulWidget:因狀態(tài)變更可以導(dǎo)致UI變更的的Widget,涉及到數(shù)據(jù)渲染場景,都使用StatefulWidget。
為啥要分兩個?StatelessWidget擁有的功能,StatefulWidget都有了啊?
答案只有一個:性能、性能、性能
在StatefulWidget里,因為要維護狀態(tài),他的生命周期比StatelessWidget更復(fù)雜,每次執(zhí)行setState,都會觸發(fā)
使用過小程序的同學(xué)在這點上應(yīng)該有體會,在小程序的官方文檔中,會強烈建議減少setData的使用頻率,以避免性能的下降。只不過flutter更是激進,推出了StatelessWidget,并直接在該Widget里砍掉了setState的使用。
頁面結(jié)構(gòu)關(guān)系如下:
6.路由
實際的項目,是有多個不同的頁面的,頁面之間的跳轉(zhuǎn),就要用到路由了。我們增加一個list頁面,點擊Home頁的“Click Me”按鈕,跳轉(zhuǎn)到列表頁list。
6.1 單個頁面的跳轉(zhuǎn)
增加list.dart
import?'package:flutter/material.dart';class?ListPage?extends?StatelessWidget?{@overrideWidget?build(BuildContext?context)?{//定義列表widget的listList<Widget>?list=<Widget>[];//Demo數(shù)據(jù)定義var?data=[{"id":1,"title":"測試數(shù)據(jù)AAA","subtitle":"ASDFASDFASDF"},{"id":2,"title":"測試數(shù)據(jù)bbb","subtitle":"ASDFASDFASDF"},{"id":3,"title":"測試數(shù)據(jù)ccc","subtitle":"ASDFASDFASDF"},{"id":4,"title":"測試數(shù)據(jù)eee","subtitle":"ASDFASDFASDF"},];//根據(jù)Demo數(shù)據(jù),構(gòu)造列表ListTile組件listfor?(var?item?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)));}//返回整個頁面return?Scaffold(appBar:?AppBar(title:?Text("List?Page"),),body:?Center(child:?ListView(children:?list,)),);} }在main.dart增加list頁面的引入
import?'list.dart';修改Home頁的按鈕事件,增加Navigator.push跳轉(zhuǎn)
FlatButton(color:?Colors.blue,textColor:?Colors.white,onPressed:?()?{????Navigator.push(context,?MaterialPageRoute(builder:(context)?{return??ListPage();}));},child:?Text("Click?ME",style:?TextStyle(fontSize:?20.0)?),)核心方法就是:
Navigator.push(context,MaterialPageRoute)
跳轉(zhuǎn)示例:
6.2 更多頁面跳轉(zhuǎn)使用路由表
在MaterialApp中,有一個屬性是routes,我們可以對路由進行命名,這樣跳轉(zhuǎn)的時候,只需要使用對應(yīng)的路由名字即可,如:Navigator.pushNamed(context, RouterName)。點擊兩個不同的按鈕,分別跳轉(zhuǎn)到ListPage,和Page2去。
Main.dart修改一下如下:
import?'package:flutter/material.dart'; import?'list.dart'; import?'page2.dart';void?main()?=>?runApp(MyApp());class?MyApp?extends?StatelessWidget?{@overrideWidget?build(BuildContext?context)?{return?MaterialApp(title:?'Flutter?Demo',//路由表定義routes:{"ListPage":(context)=>?ListPage(),"Page2":(context)=>?Page2(),},home:?MyHomePage(),);} }class?MyHomePage?extends?StatefulWidget{@overrideMyHomePageState?createState()?=>?MyHomePageState(); }class?MyHomePageState?extends?State<MyHomePage>{@overrideWidget?build(BuildContext?context)?{return?Scaffold(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)我們有了路由以后,就可以開始在一個項目里用不同的頁面,去學(xué)習(xí)不同的功能了。
6.3 路由傳參
列表頁跳轉(zhuǎn)到詳情頁,需要路由傳參,這個在flutter體系里,又是怎么做的呢?
首先,在main.dart里,增加詳情頁DedailPage的路由配置
//路由表定義routes:{"ListPage":(context)=>?ListPage(),"Page2":(context)=>?Page2(),"DetailPage":(context)=>?DetailPage(),?//增加詳情頁的路由配置},并修改ListPage里L(fēng)istTile的點擊事件,增加路由跳轉(zhuǎn)傳參,這里是將整個item數(shù)據(jù)對象傳遞
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:(){//點擊的時候,進行路由跳轉(zhuǎn)傳參Navigator.pushNamed(context,?"DetailPage",?arguments:item);},)詳情頁DetailPage里,獲取傳參并顯示
import?'package:flutter/material.dart'; class?DetailPage?extends?StatelessWidget?{@overrideWidget?build(BuildContext?context)?{//獲取路由傳參final?Map?args?=?ModalRoute.of(context).settings.arguments;return?Scaffold(appBar:?AppBar(title:?Text("Detail?Page"),),body:?new?Column(children:?<Widget>[Text("我是Detail頁面"),Text("id:${args['id']}"?),Text("id:${args['title']}"),Text("id:${args['subtitle']}")],));} }Demo效果:
7.widget
Flutter提供了很多默認(rèn)的組件,而每個組件的都繼承自widget 。在Flutter眼里:一切都是widget。這句看起來是不是很熟悉?還記得在webpack里,一切都是module嗎?類似的還有java的一切都是對象。貌似任何一個技術(shù),最后都是用哲學(xué)作為指導(dǎo)思想。
widget,作為可視化的UI組件,包含了顯示UI、功能交互兩部分。大的widget,也可以由多個小的widget組合而成。
常用的widget組件:
7.1 Text
Demo:
Text("Hello?world",style:?TextStyle(fontSize:?50,fontWeight:?FontWeight.bold,color:Color(0xFF0000ff))),Text的樣式,來自另一個widget:TextStyle。而TextStyle里的color,又是另一個widget Color的實例。
如果用flutter的縮進的方法,看起來確實有點丑陋,習(xí)慣寫CSS的前端同學(xué),可以看看下面的風(fēng)格:
Text(?"Hello?world",?style:?TextStyle(?fontSize:?50,fontWeight:?FontWeight.bold,color:Color(0xFF0000ff)?)?)寫成一行,是不是就順眼多了?這算前端惡習(xí)嗎?^_^
7.2 Button
對于flutter來說,Button就提供了很多種,我們來看看他們的區(qū)別:
RaisedButton: 凸起的按鈕
FlatButton:扁平化按鈕
OutlineButton:帶邊框按鈕
IconButton:帶圖標(biāo)按鈕
按鈕測試頁dart:
import?'package:flutter/material.dart';class?ButtonPage?extends?StatelessWidget?{@overrideWidget?build(BuildContext?context)?{return?Scaffold(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:
項目中要用哪個,就各取所需吧~
7.3 Container
Container是非常常用的一個widget,他一般是用作一個容器。我們先來看看他的基礎(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)部撐開增加留白:
Container(color:?Colors.blue,padding:?EdgeInsets.all(30),child:?Text("Hello?Container?",style:TextStyle(fontSize:?20,color:?Colors.white)),)Margin
我們還可以使用margin,在容器的外部撐開增加偏移量,
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
我們還可以給這個矩形,使用tansform做一些變化,比如,旋轉(zhuǎn)一個角度
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é)要說了,好熟悉啊。對,他就是很像Html里的一個東西:DIV,你確實可以對應(yī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ù)雜一些,首先要把圖片的路徑配置,加入到之前說過的pubspec.yaml配置文件里去。
加載本地圖片時使用AssetImage:
child:Image(image:?AssetImage("assets/images/logo.png"),width:?200.0,)??????也可以使用簡寫:
?Image.asset("assets/images/logo.png",width:200.0)flutter提供的組件很多,這里就不一一舉例說明,有興趣的還是建議大家去看API:https://api.flutter.dev/
8.布局
我們已經(jīng)了解了這么多組件,那么怎么繪制一個完整的頁面呢?這就到了頁面布局的部分了。
8.1 Row & Column & Center 行列軸布局
字面意義也很好理解,行布局、列布局、居中布局,這些布局對于Flutter來說,也都是一個個的widget。
區(qū)別在于,row、column 是有多個children的widget, 而Center是只有 1個child的 widget。
?Row(children:<Widget>[])?Column(children:<Widget>[])????Center(child:Text("Hello"))8.2 Align 角定位布局
我們常常在Container里,需要顯示的內(nèi)容在左上角,左下角,右上角,右下角。在html時代,使用CSS可以很容易的實現(xiàn),但是flutter里,必須依賴Align 這個定位的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 絕對定位
當(dāng)然還有絕對定位的需求,這在css里,使用position:absolute就搞定了,但是在flutter里,需要借助stack+ positioned兩個widget一起組合使用。
Stack: 支持元素堆疊
Positioned:支持絕對定位
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é)都熟悉,之前講過的Row,Column,其實都是繼承自Flex,也屬于流式布局。
如果軸向不確定,使用Flex,通過修改direction的值設(shè)定軸向
如果軸向已確定,使用Row,Column,布局更簡潔,更有語義化
Flex測試頁:
class?FlexPage?extends?StatelessWidget?{@overrideWidget?build(BuildContext?context)?{return?Scaffold(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,),),],),);} }示例中,軸向橫向排列,最左邊一個固定寬度的Container,右邊兩個Expanded,各自占剩下的寬度的一半。
9.動畫
Flutter既然說了,一切都是Widget,包括動畫實現(xiàn),也是一個Widget。我們還是看一個示例
9.1 簡單動畫:淡入淡出:
使用flutter提供的現(xiàn)成的Widget:
import?'package:flutter/material.dart';class?AnimatePage?extends?StatefulWidget?{_AnimatePage??createState()=>?_AnimatePage(); }?class?_AnimatePage?extends?State<AnimatePage>?{bool?_visible=true;@overrideWidget?build(BuildContext?context)?{return?Scaffold(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就是動畫透明度變化的的Widget,而被透明度控制變化的Image則是AnimatedOpacity的子元素。這個和以往前端寫動畫的方式,就完全不一樣了,需要改變一下思維方式。
Demo效果
9.2 復(fù)雜一些的動畫:放大縮小
當(dāng)寫復(fù)雜一些動畫的時候,沒有對應(yīng)的widget組件,就需要自己使用Animation,和AnimationController,以及Tween來組合。
Animation: 保存動畫的值和狀態(tài)
AnimationController: 控制動畫,包含:啟動forward()、停止stop()、反向播放reverse()等方法
Tween: 提供begin,end作為動畫變化的取值范圍
Curve:設(shè)置動畫使用曲線變化,如非勻速動畫,先加速,后減速等的設(shè)定。
動畫示例:
class?AnimatePage2?extends?StatefulWidget?{_AnimatePage??createState()=>?_AnimatePage(); }?class?_AnimatePage?extends?State<AnimatePage2>??with?SingleTickerProviderStateMixin?{Animation<double>?animation;AnimationController?controller;initState()?{super.initState();controller?=??AnimationController(duration:??Duration(seconds:?3),?vsync:?this);//使用彈性曲線,數(shù)據(jù)變化從0到300animation?=?CurvedAnimation(parent:?controller,?curve:?Curves.bounceIn);animation?=?Tween(begin:?0.0,?end:?300.0).animate(animation)..addListener(()?{setState(()?{});});//啟動動畫(正向執(zhí)行)controller.forward();}@overrideWidget?build(BuildContext?context)?{return?Scaffold(appBar:?AppBar(title:?Text("Animate?Page"),),body:?Center(child:?Image.asset("assets/images/logo.png",width:?animation.value,?height:?animation.value),)??);???}dispose()?{//路由銷毀時需要釋放動畫資源controller.dispose();super.dispose();}}很重要的一點,在路由銷毀的時候,需要釋放動畫資源,否則容易導(dǎo)致內(nèi)存泄漏。
顯示Demo:
10.http請求
做業(yè)務(wù)邏輯,總離不開http請求,接下來,就來看下flutter的http請求是如何做的。
10.1 HttpClient
httpClient在 dart:io庫中,不需要引入第三方庫就可以使用,示例代碼如下:
使用示例
import?'dart:convert'; import?'dart:io';Future?_getByHttpClient()?async{//接口地址const?url="https://www.demo.com/api";//定義httpClientHttpClient?client?=?new?HttpClient();//定義requestHttpClientRequest?request?=?await?client.getUrl(Uri.parse(url));//定義reponseHttpClientResponse?response?=?await?request.close();//respinse返回的數(shù)據(jù),是字符串String?responseBody?=?await?response.transform(utf8.decoder).join();//關(guān)閉httpClientclient.close();//字符串需要轉(zhuǎn)化為JSONvar?json=?jsonDecode(responseBody);return?json;}?總的看起來,代碼還是挺繁瑣的,使用起來并不方便。
10.2 http
這是Dart.dev提供的第三方類庫,地址:https://pub.dev/packages/http
需要先在pubspec.yaml里添加類庫應(yīng)用
dependencies:flutter:sdk:?flutterjson_annotation:?^2.0.0http:?^0.12.0+2使用示例:
Future?_getByDartHttp()?async?{//?接口地址const?url="https://www.demo.com/api";//獲取接口的返回值final?response?=?await?http.get(url);//接口的返回值轉(zhuǎn)化為JSONvar?json?=?jsonDecode(response.body);?return?json; }這種寫法,比上面的httpClient簡潔了許多。
10.3 Dio
國內(nèi)使用最廣泛的,還是flutterchina在github上提供的Dio第三方庫,目前Star達到了5800多個。
官網(wǎng)地址:https://github.com/flutterchina/dio
使用Dio,因為是第三方庫,所以同樣要先在 pubspec.yaml 添加第三方庫引用。
dependencies:flutter:sdk:?flutterjson_annotation:?^2.0.0dio:?2.1.16使用示例:
import?'package:dio/dio.dart';Future?_getByDio()?async{//?接口地址const?url="https://www.demo.com/api";//定義?Dio實例Dio?dio?=?new?Dio();//獲取dio返回的ResponseResponse?response?=?await?dio.get(url);//返回值轉(zhuǎn)化為JSONvar?json=jsonDecode(response.data);return?json; }接口調(diào)用也是比httpclient簡單很多,可能由于fluterchina在他的官方教程里,極力推薦這個dio庫,所以目前這個第三方庫的使用情況最為廣泛。和Dart.dev的http不同的是,他需要new一個Dio的實例,在創(chuàng)建實例的時候,還可以傳入更多的擴展配置參數(shù)。
BaseOptions?options?=?new?BaseOptions(baseUrl:?"https://www.xx.com/api",connectTimeout:?5000,receiveTimeout:?3000, ); Dio?dio?=?new?Dio(options);11.吐吐槽
學(xué)習(xí)Flutter的過程中,其實還是有很多坎坷和需要吐槽的地方。
11.1 墻
因為有墻在,所以在配置flutter,或者下載flutter插件和第三方庫的時候,需要墻內(nèi)外來回切換。
11.2 組件過度設(shè)計
提供的各種widget組件很多,但是真正核心的組件、常用的組件,也就哪些。比如Flex 和column、row的關(guān)系,比如,Tween 與IntTween,ColorTween,SizeTween等20多個Tween子類之間的關(guān)系,你需要花很大的精力,去看每個具體子類的實現(xiàn)差別。
11.3 嵌套太多不適應(yīng)
因為嵌套層級很多,而且布局、動畫、功能都在一起,第一次上手Flutter和Dart,這種嵌套關(guān)系讓人很暈菜,這個只能去慢慢克服。另外,多開發(fā)自定義的組件,可以讓嵌套關(guān)系看起來清晰一些。
11.4 布局修改會導(dǎo)致嵌套關(guān)系修改
前端的html+css分離世界里,不改變嵌套關(guān)系,修改CSS就可以調(diào)整布局。但是在Flutter里因為布局也是嵌套關(guān)系,這就導(dǎo)致必須去改變嵌套關(guān)系。要讓嵌套更簡單變動影響更小,頁面拆分成子組件變得尤為重要。
11.5 Dart語言升級
沒錯,語言升級也會導(dǎo)致學(xué)習(xí)的困擾,外面的資料新舊都有,比如有些是 new Text() ,有些直接是Text() ,新手上路會很暈菜。其實這都是Dart語言升級導(dǎo)致的,記住Dart升級2.X以后,都不使用new了。感興趣的可以自己去看下Dart的升級變更說明。
11.6 不能熱更新
年中的時候,Google官方宣布flutter暫不官方支持熱更新,但是閑魚團隊已經(jīng)有了自己的熱更新方案。關(guān)于熱更新,只能靜觀其變了。性能、開發(fā)效率、熱更新,總是要有取舍的。即使是閑魚團隊,熱更新也是付出了一點點性能下降的代價的,這是你選擇flutter的初衷嗎?還是那句話:權(quán)衡得失。
12.結(jié)語
這不是一篇教程,只是自己在學(xué)習(xí)Flutter過程中的一點體驗和經(jīng)歷,也因為時間關(guān)系,研究并不深入,如有疏漏,還請不吝賜教。
隨著Flutter1.9的發(fā)布,以及flutter for web的發(fā)布,Flutter的組件化思路,使得一份代碼跨三端變成可能,相信Flutter的未來會更加廣闊,也歡迎大家一起交流Flutter。
總結(jié)
以上是生活随笔為你收集整理的轻松 Flutter 入门,秒变大前端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发一个爆款 VS Code 插件这么简
- 下一篇: 是什么能让 APP 快速精准定位到我们的