[轉]入門 Flutter

隨著純客戶端到Hybrid技術,到RN&Weex,再到如今的Flutter技術娜睛,客戶端實現技術不斷前進。 在之前的一個APP項目中冕杠,因為歷史原因當時選擇了weex微姊,隨著使用的不斷深入酸茴,我們逐漸發(fā)現了weex的渲染性能問題已經成為一個隱患和瓶頸分预。 而Flutter技術的不斷成熟和流行,Flutter的良好的跨平臺性和高性能優(yōu)點薪捍,不斷吸引著我們笼痹。

1.Flutter是啥玩意兒?

Flutter是谷歌的移動UI框架酪穿,可以快速在iOS和Android上構建高質量的原生用戶界面凳干。

  • 具有跨平臺開發(fā)特性,支持IOS被济、Android救赐、Web三端。
  • 熱重載特性大大提高了開發(fā)效率
  • 自繪UI引擎和編譯成原生代碼的方式只磷,使得系統(tǒng)的運行時的高性能成為了可能
  • 使用Dart語言经磅,目前已經支持同時編譯成Web端代碼,

到底值不值得跟進Flutter技術呢钮追? 還是看下Flutter预厌,Weex,ReactNative的搜索指數對比元媚,大概就知道這個行業(yè)趨勢了轧叽。

image

藍色是Flutter,可以看出上升勢頭非常強勁刊棕。苦逼的前端就是這樣炭晒,你不跟潮流,潮流就會把你拋棄甥角。

2.移動端跨平臺技術對比

為啥會有Flutter這種東西? 他的原理是什么网严? 他是怎么做到高性能的? 要明白這些問題蜈膨,我們不得不從幾種移動端跨平臺技術的對比講起屿笼。

2.1 H5+原生APP

image

技術門檻最低牺荠,接入速度最快,熱更新最方便的驴一,自然就是H5方式休雌。APP中提供一個Webview使用H5頁面的Http直連。APP和H5可以相互獨立開發(fā)肝断,JS使用Bridge與原生進行數據通信杈曲,顯示界面依賴Webview的瀏覽器渲染。 但是帶來的問題也很明顯胸懈,因為是需要遠程直連担扑,那么初次打開H5頁面,會有瞬間的白屏趣钱,并且Webview本身會有至少幾十M的內存消耗涌献。

當然,作為前端開發(fā)人員首有,在H5方式可以使用SPA單頁面燕垃、懶加載、離線H5等各種前端優(yōu)化手段進行性能優(yōu)化井联,以使得H5的表現更接近原生卜壕。但是首次的瞬間白屏和內存,Bridge的通信效率低下烙常,始終是被技術框架給局限住了轴捎。

2.2 RN&Weex

image

由于H5的那些弊端,愛折騰的前端工程師蚕脏,祭出了RN侦副、Weex兩個大殺器, 使用原生去解析RN蝗锥、Weex的顯示配置跃洛,顯示層、邏輯層都直接與原生數據通信终议。 因為拋棄了瀏覽器汇竭,自然渲染性能、執(zhí)行性能都提升了一大截穴张。

但是细燎,每次遇到顯示的變更,JS都還會通過Bridge和原生轉一道再做渲染的調整皂甘,所以Bridge就最后成為了性能的瓶頸玻驻。在實際項目中,特別是做一些大量復雜動畫處理的時候,由于渲染部分需要頻繁通信璧瞬,性能問題變得尤為突出户辫。 有興趣的同學可以去看看BindingX,里面有關于動畫中數據通信效率低下導致動畫幀率低下的詳細說明嗤锉。

2.3 Flutter

image

不得不佩服Google開發(fā)人員的想象力渔欢,為了達到極致性能,Flutter更前進了一步瘟忱,Flutter代碼編譯完成以后奥额,直接就是原生代碼,并且使用自繪UI引擎原生方式做渲染访诱。 Flutter依賴一個Skia 2D圖形化引擎垫挨。Skia也是Android平臺和Chrome的底層渲染引擎,所以性能方面完全不用擔心触菜。因為使用Dart做AOT編譯成原生九榔,自然也比使用解釋性的JS在V8引擎中執(zhí)行性能更快,并且因為去掉Bridge玫氢,沒有了繁瑣的數據通信和交互帚屉,性能就更前進了一步。

3.Dart語言

學習Flutter漾峡,得先了解Dart。Dart語言曾經雄心勃勃的要替換Javascript, 但是發(fā)布的時機正好遇到JS的飛速發(fā)展喻旷,于是就逐漸沉寂生逸,直到配合Flutter的發(fā)布,才又重新煥發(fā)了生機且预。

在最近2019年9月的一次Google開發(fā)者大會中槽袄,伴隨著Flutter1.9的發(fā)布,目前的Dart也同時更新到了2.5版本锋谐, 提供了機器學習和對C跨平臺調用的能力遍尺。總體來說涮拗,Dart語法乾戏,對于前端同學,上手還是很容易的三热,風格很像鼓择。

image

關于Dart語法,請移步傳送門:https://dart.dev/samples

4.環(huán)境配置

無論學什么新語言就漾,首先都是環(huán)境配置呐能。由于Flutter出自Google,所以有一定門檻抑堡,如果在公司內安裝摆出,你還需要一個方便的代理切換工具, 比如:Proxifier 朗徊。

安裝教程,參照官網:https://flutter.dev/docs/get-started/install

Flutter支持多種編輯器如:Android Studio , XCode偎漫。 但是既然作為支持跨雙端的開發(fā)荣倾,個人還是推薦使用 VSCode

VSCode安裝完成后骑丸,需要安裝Flutter插件舌仍,和Dart插件. 在擴展窗口里,搜索Flutter通危,和Dart铸豁,點擊“Install”即可,非常方便菊碟。

image

如果安裝不上去节芥,記得開啟下代理。

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 即可

image
image

注意請先打開代理,否則你的創(chuàng)建進度魄幕,會一直被卡住相艇。

5.2 項目結構

將項目先拖入VSCode,看下目錄結構纯陨。自動創(chuàng)建完成的項目中坛芽,我們看到已經自帶了Android,IOS相關的運行環(huán)境翼抠。

入口主文件是main.dart. 可以打開來先熟悉下咙轩,暫時不了解沒關系,后面再講阴颖。

還有一個重要的文件是pubspec.yaml 活喊,是項目的配置文件,這個后續(xù)也會做修改量愧。

image

5.3 啟動模擬器

點擊VSCode右下角的模擬器钾菊,啟動模擬器。(VSCode會自動找到Android環(huán)境侠畔、IOS環(huán)境下的模擬器结缚,以及真機環(huán)境)

image

5.4 啟動項目APP

image

選中Main.dart, 點擊Debug-> Start Debugging , 項目就會啟動調試软棺,并在模擬器里運行红竭。

image

5.5 簡化版的Hello World

講道理,Flutter一上來就用StatefulWidget做一個自增的Demo,其實是對新手不太友好茵宪。 我還是喜歡循序漸進最冰,先刪掉那些復雜的自增邏輯,我們基于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.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget{
   @override
  Widget build(BuildContext context) {
       return Scaffold(
            appBar: AppBar(
              title: Text("我是Title"),
            ),
            body: Center(
                    child: Text(
                        'Hello World',
                    )
            )
      );
  }  
}

在上面的代碼中,可以清楚看到凰狞,最簡單的頁面的層級關系:

MaterialApp -> MyHomePage -> Scaffold -> body -> Center -> Text

Scaffold是啥责嚷?他是Flutter的頁面腳手架端圈,你可以當HTML頁面一樣去理解远剩,不同的是表鳍,他除了Body以外,還提供appBar頂部TitleBar逾冬、bottomNavigationBar底部導航欄等屬性黍聂。

顯示效果:

image

這是最簡單的頁面,沒有交互身腻,只有顯示产还,但是實際業(yè)務場景中,是不太可能都是這種頁面的嘀趟,頁面上的數據一般都是來自接口返回脐区,然后再在頁面上進行動態(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.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget{
  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage>{
   var msg="Hello World"; //msg默認文字
   @override
   Widget build(BuildContext context) {
       return Scaffold(
            appBar: AppBar(
              title: Text("我是Title"),
            ),
            body: Center(
                      child:Column(
                              children:<Widget>[
                                  Text(msg), //根據變量值尤溜,顯示文字
                                  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í)行效果:

image

上面最關鍵的一段代碼就是這個:

 onPressed: () {
         setState(() {
                this.msg="You Click ME";
          });
 },

相信寫過小程序的同學汗唱,對這個 setState 還是很眼熟的 _

5.7 小結一下

StatelessWidget:無狀態(tài)變更宫莱,UI靜態(tài)固化的Widget, 頁面渲染性能更高哩罪。
StatefulWidget:因狀態(tài)變更可以導致UI變更的的Widget授霸,涉及到數據渲染場景,都使用StatefulWidget际插。

為啥要分兩個碘耳? StatelessWidget擁有的功能,StatefulWidget都有了翱虺凇辛辨?

答案只有一個:性能、性能、性能

在StatefulWidget里斗搞,因為要維護狀態(tài)指攒,他的生命周期比StatelessWidget更復雜,每次執(zhí)行setState僻焚,都會觸發(fā)
window.scheduleFrame() 導致整個頁面的widget被刷新允悦,性能就會降低。

使用過小程序的同學在這點上應該有體會虑啤,在小程序的官方文檔中隙弛,會強烈建議減少setData的使用頻率,以避免性能的下降狞山。 只不過flutter更是激進全闷,推出了StatelessWidget,并直接在該Widget里砍掉了setState的使用铣墨。

頁面結構關系如下:

image

6.路由

實際的項目室埋,是有多個不同的頁面的,頁面之間的跳轉伊约,就要用到路由了姚淆。 我們增加一個list頁面,點擊Home頁的“Click Me”按鈕屡律,跳轉到列表頁list腌逢。

6.1 單個頁面的跳轉

增加list.dart

import 'package:flutter/material.dart';

class ListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //定義列表widget的list
    List<Widget> list=<Widget>[];

    //Demo數據定義
    var data=[
      {"id":1,"title":"測試數據AAA","subtitle":"ASDFASDFASDF"},
      {"id":2,"title":"測試數據bbb","subtitle":"ASDFASDFASDF"},
      {"id":3,"title":"測試數據ccc","subtitle":"ASDFASDFASDF"},
      {"id":4,"title":"測試數據eee","subtitle":"ASDFASDFASDF"},
    ];

    //根據Demo數據,構造列表ListTile組件list
    for (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跳轉

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)

跳轉示例:

image

6.2 更多頁面跳轉使用路由表

在MaterialApp中搏讶,有一個屬性是routes,我們可以對路由進行命名霍殴,這樣跳轉的時候媒惕,只需要使用對應的路由名字即可,如:Navigator.pushNamed(context, RouterName)来庭。點擊兩個不同的按鈕妒蔚,分別跳轉到ListPage,和Page2去月弛。

Main.dart修改一下如下

import 'package:flutter/material.dart';
import 'list.dart';
import 'page2.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      //路由表定義
      routes:{
        "ListPage":(context)=> ListPage(),
        "Page2":(context)=> Page2(),
      },
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget{
  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage>{
   @override
   Widget build(BuildContext context) {
       return Scaffold(
            appBar: AppBar(
              title: Text("我是Title"),
            ),
            body: Center(
                      child:Column(
                              children:<Widget>[
                                  RaisedButton(
                                      child: Text("Clikc to ListPage" ),
                                      onPressed: () {
                                        //根據命名路由做跳轉
                                         Navigator.pushNamed(context, "ListPage");
                                      },
                                  ),
                                   RaisedButton(
                                      child: Text("Click to Page2" ),
                                      onPressed: () {
                                          //根據命名路由做跳轉
                                         Navigator.pushNamed(context, "Page2");
                                      },
                                  )

                              ]
                      )
                  )
      );
  }  

}

示例:

image

當我們有了路由以后肴盏,就可以開始在一個項目里用不同的頁面,去學習不同的功能了帽衙。

6.3 路由傳參

列表頁跳轉到詳情頁菜皂,需要路由傳參,這個在flutter體系里厉萝,又是怎么做的呢恍飘?

首先榨崩,在main.dart里,增加詳情頁DedailPage的路由配置

//路由表定義
      routes:{
        "ListPage":(context)=> ListPage(),
        "Page2":(context)=> Page2(),
        "DetailPage":(context)=> DetailPage(), //增加詳情頁的路由配置
      },

并修改ListPage里ListTile的點擊事件常侣,增加路由跳轉傳參蜡饵,這里是將整個item數據對象傳遞

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:(){
            //點擊的時候,進行路由跳轉傳參
             Navigator.pushNamed(context, "DetailPage", arguments:item);
          },
      )

詳情頁DetailPage里胳施,獲取傳參并顯示

import 'package:flutter/material.dart';
class DetailPage extends StatelessWidget {
  @override
  Widget 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效果:

image

7.widget

Flutter提供了很多默認的組件溯祸,而每個組件的都繼承自widget 。 在Flutter眼里:一切都是widget舞肆。 這句看起來是不是很熟悉焦辅? 還記得在webpack里,一切都是module嗎椿胯? 類似的還有java的一切都是對象筷登。貌似任何一個技術,最后都是用哲學作為指導思想哩盲。

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的縮進的方法,看起來確實有點丑陋眨猎,習慣寫CSS的前端同學抑进,可以看看下面的風格:

Text( "Hello world", style: TextStyle( fontSize: 50,fontWeight: FontWeight.bold,color:Color(0xFF0000ff) ) )

寫成一行,是不是就順眼多了睡陪?這算前端惡習嗎单匣?_

image

7.2 Button

對于flutter來說,Button就提供了很多種宝穗,我們來看看他們的區(qū)別:

RaisedButton: 凸起的按鈕
FlatButton:扁平化按鈕
OutlineButton:帶邊框按鈕
IconButton:帶圖標按鈕

按鈕測試頁dart:

import 'package:flutter/material.dart';

class ButtonPage extends StatelessWidget {
  @override
  Widget 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:

image

項目中要用哪個,就各取所需吧~

7.3 Container

Container是非常常用的一個widget码秉,他一般是用作一個容器逮矛。我們先來看看他的基礎屬性,順便可以想想他像HTML里的啥转砖?

基礎屬性: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)),
        )
      )

image

Padding

我們也可以不設置寬高晋控,用padding在內部撐開增加留白:

Container(
           color: Colors.blue,
           padding: EdgeInsets.all(30),
           child: Text("Hello Container ",style:TextStyle(fontSize: 20,color: Colors.white)),

        )

image

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)),
        )

image

Transform

我們還可以給這個矩形赡译,使用tansform做一些變化仲吏,比如,旋轉一個角度

Container(
           color: Colors.blue,
           padding: EdgeInsets.all(30),
           child: Text("Hello Container ",style:TextStyle(fontSize: 20,color: Colors.white)),
           transform: Matrix4.rotationZ(0.5)
        )

image

看到這里蝌焚,好多前端同學要說了裹唆,好熟悉啊。 對只洒,他就是很像Html里的一個東西:DIV许帐,你確實可以對應的去加強理解。

7.4 Image

網絡圖片加載

使用NetworkImage毕谴,可以做網絡圖片的加載:

child:Image(
          image: NetworkImage("https://mat1.gtimg.com/pingjs/ext2020/qqindex2018/dist/img/qq_logo_2x.png"),
           width: 200.0,
        )  

image

本地圖片加載

加載本地圖片成畦,就稍微復雜一些,首先要把圖片的路徑配置涝开,加入到之前說過的pubspec.yaml配置文件里去:

image

加載本地圖片時使用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.布局

我們已經了解了這么多組件忠寻,那么怎么繪制一個完整的頁面呢惧浴? 這就到了頁面布局的部分了。

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里家乘,需要顯示的內容在左上角,左下角藏澳,右上角仁锯,右下角。 在html時代翔悠,使用CSS可以很容易的實現业崖,但是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)),
                  )
        )

顯示效果:

image

Alignment提供了多種定位供選擇,還算是很貼心的双炕。

image

8.3 Stack & Positioned 絕對定位

當然還有絕對定位的需求狞悲,這在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)
                  )
              ],
            )

image

8.4 Flex & Expanded 流式布局

Flex流式布局作為前端同學都熟悉趟济,之前講過的Row乱投,Column,其實都是繼承自Flex顷编,也屬于流式布局戚炫。

如果軸向不確定,使用Flex媳纬,通過修改direction的值設定軸向
如果軸向已確定双肤,使用Row,Column钮惠,布局更簡潔茅糜,更有語義化

Flex測試頁:

class FlexPage extends StatelessWidget {
  @override
  Widget 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,各自占剩下的寬度的一半预明。

image

9.動畫

Flutter既然說了缩赛,一切都是Widget,包括動畫實現撰糠,也是一個Widget酥馍。 我們還是看一個示例

9.1 簡單動畫:淡入淡出:

使用flutter提供的現成的Widget:

import 'package:flutter/material.dart';

class AnimatePage extends StatefulWidget {
  _AnimatePage  createState()=> _AnimatePage();
} 

class _AnimatePage extends State<AnimatePage> {
  bool _visible=true;
  @override
  Widget 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效果

image

9.2 復雜一些的動畫:放大縮小

當寫復雜一些動畫的時候砚尽,沒有對應的widget組件,就需要自己使用Animation辉词,和AnimationController尉辑,以及Tween來組合。

Animation: 保存動畫的值和狀態(tài)
AnimationController: 控制動畫较屿,包含:啟動forward()隧魄、停止stop()、反向播放reverse()等方法
Tween: 提供begin隘蝎,end作為動畫變化的取值范圍
Curve:設置動畫使用曲線變化购啄,如非勻速動畫,先加速嘱么,后減速等的設定狮含。

動畫示例:

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);

     //使用彈性曲線,數據變化從0到300
     animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
     animation = Tween(begin: 0.0, end: 300.0).animate(animation)
      ..addListener(() {
        setState(() {
        });
      });

    //啟動動畫(正向執(zhí)行)
    controller.forward();
  }

  @override
  Widget 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();
  }

}

很重要的一點曼振,在路由銷毀的時候几迄,需要釋放動畫資源,否則容易導致內存泄漏冰评。

顯示Demo:

image

10.http請求

做業(yè)務邏輯映胁,總離不開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";

    //定義httpClient
    HttpClient client = new HttpClient();
    //定義request
    HttpClientRequest request = await client.getUrl(Uri.parse(url));
    //定義reponse
    HttpClientResponse response = await request.close();
    //respinse返回的數據弛姜,是字符串
    String responseBody = await response.transform(utf8.decoder).join();
    //關閉httpClient
    client.close();
    //字符串需要轉化為JSON
    var json= jsonDecode(responseBody);
    return json;

} 

總的看起來,代碼還是挺繁瑣的妖枚,使用起來并不方便廷臼。

10.2 http

這是Dart.dev提供的第三方類庫,地址:https://pub.dev/packages/http

需要先在pubspec.yaml里添加類庫應用

dependencies:
  flutter:
    sdk: flutter
  json_annotation: ^2.0.0
  http: ^0.12.0+2

使用示例:

Future _getByDartHttp() async {
  // 接口地址
 const url="https://www.demo.com/api";//獲取接口的返回值
 final response = await http.get(url);
 //接口的返回值轉化為JSON
 var json = jsonDecode(response.body); 
 return json;
}

這種寫法绝页,比上面的httpClient簡潔了許多荠商。

Dio

國內使用最廣泛的,還是flutterchina在github上提供的Dio第三方庫抒寂,目前Star達到了5800多個结啼。

官網地址:https://github.com/flutterchina/dio

使用Dio,因為是第三方庫屈芜,所以同樣要先在 pubspec.yaml 添加第三方庫引用郊愧。

dependencies:
  flutter:
    sdk: flutter
  json_annotation: ^2.0.0
  dio: 2.1.16

使用示例:

import 'package:dio/dio.dart';

Future _getByDio() async{

      // 接口地址
      const url="https://www.demo.com/api";

      //定義 Dio實例
      Dio dio = new Dio();
      //獲取dio返回的Response
      Response response = await dio.get(url);
      //返回值轉化為JSON
      var json=jsonDecode(response.data);
      return json;
}

接口調用也是比httpclient簡單很多,可能由于fluterchina在他的官方教程里井佑,極力推薦這個dio庫属铁,所以目前這個第三方庫的使用情況最為廣泛。和Dart.dev的http不同的是躬翁,他需要new一個Dio的實例焦蘑,在創(chuàng)建實例的時候,還可以傳入更多的擴展配置參數盒发。

BaseOptions options = new BaseOptions(
    baseUrl: "https://www.xx.com/api",
    connectTimeout: 5000,
    receiveTimeout: 3000,
);
Dio dio = new Dio(options);

11.吐吐槽

學習Flutter的過程中例嘱,其實還是有很多坎坷和需要吐槽的地方狡逢。

11.1 墻

因為有墻在,所以在配置flutter拼卵,或者下載flutter插件和第三方庫的時候奢浑,需要墻內外來回切換。

11.2 組件過度設計

提供的各種widget組件很多腋腮,但是真正核心的組件雀彼、常用的組件,也就哪些即寡。 比如Flex 和column徊哑、row的關系,比如聪富,Tween 與IntTween莺丑,ColorTween,SizeTween等20多個Tween子類之間的關系善涨,你需要花很大的精力窒盐,去看每個具體子類的實現差別。

11.3 嵌套太多不適應

因為嵌套層級很多钢拧,而且布局蟹漓、動畫、功能都在一起源内,第一次上手Flutter和Dart葡粒,這種嵌套關系讓人很暈菜,這個只能去慢慢克服膜钓。 另外嗽交,多開發(fā)自定義的組件,可以讓嵌套關系看起來清晰一些颂斜。

11.4 布局修改會導致嵌套關系修改

前端的html+css分離世界里夫壁,不改變嵌套關系,修改CSS就可以調整布局沃疮。 但是在Flutter里因為布局也是嵌套關系盒让,這就導致必須去改變嵌套關系。 要讓嵌套更簡單變動影響更小司蔬,頁面拆分成子組件變得尤為重要蔓搞。

11.5 Dart語言升級

沒錯艾帐,語言升級也會導致學習的困擾,外面的資料新舊都有没隘,比如有些是 new Text() ,有些直接是Text() 扬卷,新手上路會很暈菜。 其實這都是Dart語言升級導致的,記住Dart升級2.X以后,都不使用new了浮梢。感興趣的可以自己去看下Dart的升級變更說明。

11.6 不能熱更新

年中的時候泉手,Google官方宣布flutter暫不官方支持熱更新黔寇,但是閑魚團隊已經有了自己的熱更新方案。 關于熱更新斩萌,只能靜觀其變了。 性能屏轰、開發(fā)效率颊郎、熱更新,總是要有取舍的霎苗。即使是閑魚團隊姆吭,熱更新也是付出了一點點性能下降的代價的,這是你選擇flutter的初衷嗎唁盏?還是那句話:權衡得失内狸。

12.結語

隨著 9 月谷歌發(fā)布 Flutter1.9 以及flutter for web,Flutter的組件化思路厘擂,使得一份代碼跨三端變成可能昆淡,相信Flutter的未來會更加廣闊。

這不是一篇教程刽严,只是在學習Flutter過程中的一點體驗和經歷昂灵,也因為時間關系,研究并不深入舞萄,如有疏漏眨补,還請不吝賜教。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末倒脓,一起剝皮案震驚了整個濱河市撑螺,隨后出現的幾起案子,更是在濱河造成了極大的恐慌崎弃,老刑警劉巖甘晤,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異吊履,居然都是意外死亡安皱,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門艇炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌伊,“玉大人,你說我怎么就攤上這事【幼” “怎么了虹脯?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奏候。 經常有香客問我循集,道長,這世上最難降的妖魔是什么蔗草? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任咒彤,我火速辦了婚禮,結果婚禮上咒精,老公的妹妹穿的比我還像新娘镶柱。我一直安慰自己,他們只是感情好模叙,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布歇拆。 她就那樣靜靜地躺著,像睡著了一般范咨。 火紅的嫁衣襯著肌膚如雪故觅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天渠啊,我揣著相機與錄音输吏,去河邊找鬼。 笑死昭抒,一個胖子當著我的面吹牛评也,可吹牛的內容都是我干的。 我是一名探鬼主播灭返,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盗迟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了熙含?” 一聲冷哼從身側響起罚缕,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怎静,沒想到半個月后邮弹,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蚓聘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年腌乡,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夜牡。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡与纽,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情急迂,我是刑警寧澤影所,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站僚碎,受9級特大地震影響猴娩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜勺阐,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一卷中、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渊抽,春花似錦仓坞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徙瓶。三九已至毛雇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侦镇,已是汗流浹背灵疮。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壳繁,地道東北人震捣。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像闹炉,于是被迫代替她去往敵國和親蒿赢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容

  • 國慶后面兩天在家學習整理了一波flutter渣触,基本把能擼過能看到的代碼都過了一遍羡棵,此文篇幅較長,建議保存(star...
    Nealyang閱讀 4,338評論 1 17
  • 1 Widget只是UI元素的一個配置數據嗅钻,并且一個Widget可以對應多個ElementWidget實際上就是E...
    你飛躍俊杰閱讀 713評論 0 2
  • 如何安裝 Flutter 請點擊這里 1皂冰、Flutter 是什么? Flutter是一款移動應用程序SDK养篓,包含框...
    大王叫我來巡山_Cong閱讀 1,355評論 0 9
  • 出行一個半月秃流,飲食起居簡單至極,跋山涉水也頗為勞累柳弄,卻從不曾覺得自己是在無謂地吃苦舶胀,也從不曾閃過一絲退卻的念頭。倒...
    木下亦佛閱讀 315評論 9 7
  • 小伙伴們,你們有過這樣的感受嗎峻贮?受到過這些聲音的擾亂和煩心嗎席怪?你們是如何來解決的呢? 我曾經就有過這樣痛徹心扉的感...
    演講教練姚樺閱讀 120評論 0 0