國慶後面兩天在家學習整理了一波flutter,基本把能擼過能看到的代碼都過了一遍, 此文篇幅較長,建議保存(star)再看。傳送門: Nealyang personal blog
畢竟前端出生,找(qi)到(shi)了(bing)感(mei)覺(ru)後(men),其實就是一個UI框架,只不過他的引擎基於C++,底層基於Skia渲染,DartVM虛擬機以及Text and so on...css
2018年6月21日Google發佈Flutter首個release預覽版,做爲Google baba大力推出的一種全新的響應式,跨平臺,高性能的移動開發框架,勢必會火一波~沒別的,就是由於Google baba,固然,從目前看來也的確愈來愈火了。
[Questions tagged [flutter]](https://stackoverflow.com/que...
html
本文咱們從介紹flutter基本概念到梳理經常使用Widget到經常使用app demos編寫到~放棄~,但願能夠幫助每個像我同樣的初學者。有誤地方還望大神不吝賜教~前端
國際慣例,吹一波先~react
直接移步Flutter官宣pptandroid
做爲Flutter入門文章,Dart必然少不了,固然,做爲Flutter入門篇,Dart預發基礎必然不會過多介紹。git
Dart入門傳送門:Dart or Dart2,或者從Dart中文網中學習也不錯其實.github
這裏咱們說說爲啥是Dart。web
許多語言科學家認爲,一我的說的天然語言會影響他們的思惟方式。早起Flutter團隊評估了十多種語言最終選擇了Dart,由於它符合他們構建用戶界面的方式。編程
具體選擇Dart的緣由,以及向瞭解Dart的,移步爲何Flutter會選擇 Dart瀏覽器
剛開始接觸flutter心中不免會有疑惑,不是已經有RN、Weex等各類跨平臺移動開發 了,flutter優點在哪呢? 看我從網上盜的圖!
有一種說法認爲函數式語言和命令式語言的不一樣在於命令式語言是給計算機下達指令而函數式語言是向計算機描述邏輯。這種思路在Flutter UI中獲得了體現。Flutter不提倡去操做UI,它固然也基本不會提供操做View的API,好比咱們常見的相似TextView.setText(),Button.setOnClick()這種是不會有的。對界面的描述是能夠數據化的(相似XML,JSON等),而對界面的操做是很難數據化的,這很重要,響應式須要方即可持續的將數據映射成界面。
在Flutter中用Widget來描述界面,Widget只是View的「配置信息」,編寫的時候利用Dart語言一些聲明式特性來獲得相似結構化標記語言的可讀性。Widget根據佈局造成一個層次結構。每一個widget嵌入其中,並繼承其父項的屬性。沒有單獨的「應用程序」對象,相反,根widget扮演着這個角色。在Flutter中,一切皆爲Widget,甚至包括css樣式。
<div class="greybox"> Lorem ipsum </div> .greybox { background-color: #e0e0e0; /* grey 300 */ width: 320px; height: 240px; font: 900 24px Georgia; }
在flutter中咱們編寫爲
var container = new Container( // grey box child: new Text( "Lorem ipsum", style: new TextStyle( fontSize: 24.0 fontWeight: FontWeight.w900, fontFamily: "Georgia", ), ), width: 320.0, height: 240.0, color: Colors.grey[300], );
能夠看到咱們css樣式中的font定義的樣式,在flutter中,須要new TextStyle
,TextStyle就是一個Widget,而且樣式必須做用與Container中的child:text上,不存在web中樣式的繼承。
剛開始接觸的同窗就類比於react中扯的,一切皆爲組件吧,其實widget是對頁面UI的一種描述。他功能類有點似於android中的xml,react中的jsx。widget在渲染的時候會轉化成element。Element相比於widget增長了上下文的信息。element是對應widget,在渲染樹的實例化節點。因爲widget是immutable的,因此同一個widget能夠同時描述多個渲染樹中的節點。可是Element是描述固定在渲染書中的某一個特定位置的點。簡單點說widget做爲一種描述是能夠複用的,可是element卻跟須要繪製的節點一一對應。那element是最終渲染的view麼?抱歉,還不是。element繪製時會轉化成rendObject。RendObject纔是真正通過layout和paint並繪製在屏幕上的對象。在flutter中有三套渲染相關的tree,分別是:widget tree, element tree & rendObject tree。三者的渲染流程以下:
有沒有一種 jsx -> virtual Dom -> real dom滴感受呢~
咳咳,後面會介紹基礎經常使用的Widget配合一些demo,你們可能對這個體會就會更加清晰一些。
Flutter中不少借鑑了react的思想,甚至包括後面會說到的state。
Widget自己一般由許多更小的、單一的小小widget組成,甚至小到它單一下來並無什麼做用的感受,這些Widget几几組合造成一個強大的自定義的大大Widget。
好比一個Container,對於Web FE來講可能就是個div,而他就是由不少的widget組成,這些widget負責佈局、繪製、定位、大小等。咱們可使用各類姿式來組合他們而不是繼承他們。類層次結構很淺且很寬,能夠最大限度的增長可能組合的數量
上面的圖片是Flutter分層框架結構圖,對大部分開發者而言,最經常使用的是Widgets層,屏幕上可見與不可見的元素都由Widgets層實現,這些元素被稱爲Widget。在Widgets層在上層,有兩個現成的Widget庫,Material庫即Material Design的Widget庫,Material Design是Google I/O 2014發佈的設計語言,目前成爲統一Android Mobile、Android Table、Desktop Chrome等平臺的設計語言規範。Cupertino庫則是一個模仿iOS設計風格的Widget庫。
底層是Flutter Engine虛擬機,在這一層次中須要瞭解一下的是Skia,Skia是Google研發的包括圖形、文本、圖像、動畫等多方面的圖形引擎,不只用於Google Chrome瀏覽器,Android系統也採用Skia做爲繪圖處理引擎。
GPU渲染:
state生命週期:
做爲初學者看上面的圖有點雲裏霧裏的,且先作到內心有數~
關於Flutter環境問題這裏再也不贅述
此後~大量代碼來襲
國際慣例,hello world
import 'package:flutter/material.dart'; class MyAppBar extends StatelessWidget{ MyAppBar({this.title});// final Widget title; @override Widget build(BuildContext context){ return new Container( height: 56.0, padding: const EdgeInsets.symmetric(horizontal:8.0), decoration: new BoxDecoration( color:Colors.blue[400] ), child: Row( children: <Widget>[ new IconButton( icon:new Icon(Icons.menu), tooltip:'Navigation menu', onPressed: (){ print('點擊Menu'); }, ), new Expanded( child:new Center( child:title ) ), new IconButton( icon:Icon(Icons.search), tooltip:'Search', onPressed: (){ print('點擊搜索按鈕'); }, ) ], ), ); } } class MyScaffold extends StatelessWidget{ @override Widget build(BuildContext context){ return Material( child: new Column( children:<Widget>[ new MyAppBar( title:new Text( 'Hello World', style:Theme.of(context).primaryTextTheme.title ), ), new Expanded( child:new Center( child:Text('Hello World!!!') ) ) ] ), ); } } void main(){ runApp( new MaterialApp( title:'My app', home:new MyScaffold() ) ); }
代碼地址:https://github.com/Nealyang/flutter
這個UI的確有些對不起人了,上面的title被擋住了。且先不去適配,後面咱們使用Material提供的Scaffold便可
第一個例子,重點說下代碼(用過的Widget記住):
new
無關緊要。類MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等經常使用Widget
String title
而後在類中本身去new Title(title)
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // app的根Widget @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( // 這是設置的app主題 // 運行後你能夠看到app有一個藍色的toobar,而且在不退出app的狀況下修改代碼會熱更新 primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); // 這是應用中一個基類,繼承自StateFulWidget,意味着這個類擁有一個state對象,該對象裏的一些字段會影響app的UI // 這個類是state的一些配置項。經過構造函數來獲取值,這個值通常在State中消費,而且使用final關鍵字。其實相似於react中的defaultProps final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // setState方法告訴Flutter,這個State中有些值發生了變化,以便及時將新值更新到UI上, // 若是我不經過setState更改_count字段,那麼Flutter並不會調用build匿名函數去更新界面 _counter++; }); } @override Widget build(BuildContext context) { // build方法會在每次setState的時候從新運行,例如上面的_incrementCounter方法被調用 //Flutter已經被優化了從新構建的方法,因此你只會去更新須要去更新的部分,沒必要去單獨更新裏面的一些更細小的widget,相似於React中diff return new Scaffold( appBar: new AppBar( // 這裏咱們使用從App.build方法中初始化MyHomePage時候傳入的title值來設置咱們的title title: new Text(widget.title), ), body: new Center( // Center是一個佈局Widget,他只有一個child(區分row or cloumn等是children),而且會將child的widget居中顯示 child: new Column( // Column也是一個佈局widget,他能夠有多個子widget // Column 有不少的屬性去控制他的大小以及子widget的位置,這裏咱們使用mainAxisAlignment來讓children在垂直線上居中, // 這裏的主軸就是垂直的,由於Column就是垂直方向的,這裏能夠大概想象爲display:flex,flex-directions:column,align-item,justifyContent。。。 mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'Hello World!', style:TextStyle( fontSize:24.0, color: Colors.redAccent, decorationStyle:TextDecorationStyle.dotted, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, decoration: TextDecoration.underline ) ), new Text( 'You have pushed the button this many times:', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ),//最後這個逗號有利於格式化代碼 ); } }
註釋上基本已經加了,這裏重點說下,StatefulWidget和StatelessWidget.
還有關於key的部分這裏就不作介紹了,其實就相似與react中key的概念,便於diff,提升效率的。
具體能夠查看 Key
到這裏,咱們看到了Flutter的一些基本用法,Widget的套用、樣式的編寫、事件的註冊,若是再學習下一些路由、請求、緩存是否是就能夠本身開發APP了呢
OK,強化下編寫界面,咱再來些demo吧~
本身寫的後,發現跟官網實現方式不一樣,代碼地址
具體實現能夠參照官網教程
這裏再也不贅述,下面咱們說下對於佈局的理解和感覺以及經常使用佈局widget。
從一個前端的角度來講,說到畫界面,可能仍是對佈局這塊比較敏感
)
固然,這裏咱們仍是說下目前經常使用的flex佈局,基本拿到頁面從大到小拆分後就是如上圖。
因此Widget佈局其實也就是Row和Column用的最多,而後因爲Flutter一切皆爲組件的理念,可能會須要用到別的類css佈局的Widget,譬如:Container。其實咱就理解爲塊元素吧!
下面簡單演示下一些經常使用的Widget,這裏就不在贅述Row和Column了。傳送門:佈局Widget
能夠添加padding、margin、border、background color、一般用於裝飾其餘Widget
class MyHomePage extends StatelessWidget{ @override Widget build(BuildContext context){ Container cell (String imgSrc){ return new Container( decoration: new BoxDecoration( border:Border.all(width:6.0,color:Colors.black38), borderRadius: BorderRadius.all(const Radius.circular(8.0)) ), child: Image.asset( 'images/$imgSrc', width: 180.0, height: 180.0, fit: BoxFit.cover, ), ); } return Container( padding: const EdgeInsets.all(10.0), color: Colors.grey, child: new Column( mainAxisSize: MainAxisSize.min, children:<Widget>[ new Container( margin: const EdgeInsets.only(bottom:10.0), child: new Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children:<Widget>[ cell('1.jpg'), cell('2.jpg') ] ), ), new Container( margin: const EdgeInsets.only(bottom:10.0), child: new Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children:<Widget>[ cell('3.jpg'), cell('4.jpg') ] ), ), ] ), ); } }
該佈局中每一個圖像使用一個Container來添加一個圓形的灰色邊框和邊距。而後使用容器將列背景顏色更改成淺灰色。
可滾動的網格佈局,理解爲display:grid
GridView提供兩個預製list,當GridView檢測到內容太長時,會自動滾動。若是須要構建自定義grid,但是使用GridView.count
或 GridView.extent
來指定容許設置的列數以及指定項最大像素寬度。
List<Container> _buildGridTileList(int count) { return new List<Container>.generate( count, (int index) => new Container(child: new Image.asset('images/${index+1}.jpg'))); } Widget buildGrid() { return new GridView.extent( maxCrossAxisExtent: 150.0, padding: const EdgeInsets.all(4.0), mainAxisSpacing: 4.0, crossAxisSpacing: 4.0, children: _buildGridTileList(10)); } class MyHomePage extends StatelessWidget{ @override Widget build(BuildContext context){ return new Center( child: buildGrid(), ); } }
如上是指定maxCrossAxisExtent
,咱們能夠直接去指定列數,例如官網的代碼實例:
new GridView.count( primary: false, padding: const EdgeInsets.all(20.0), crossAxisSpacing: 10.0, crossAxisCount: 3, children: <Widget>[ const Text('He\'d have you all unravel at the'), const Text('Heed not the rabble'), const Text('Sound of screams but the'), const Text('Who scream'), const Text('Revolution is coming...'), const Text('Revolution, they...'), ], )
經過crossAxisCount
直接指定列數。
層疊佈局,position爲absolute的感jio~
使用Stack來組織須要重疊的widget。widget能夠徹底或部分重疊底部widget。子列表中的第一個widget是base widget; 隨後的子widget被覆蓋在基礎widget的頂部。Stack的內容不能滾動。有點相似於weex中的設置了absolute的感受。底部組件永遠在上面組件的上面。
可滾動的長列表,能夠水平或者垂直。
Material風格組件,卡片,AntD啥的組件庫常常會出現的那種組件。
在flutter中,Card具備圓角和陰影,更改Card的elevation屬性能夠控制陰影效果。
Material風格組件,我理解爲經常使用的列表Item的樣式,最多三行文字,可選的行前、行尾的圖標
從目前我我的淺薄的Flutter技能來講,最大的困難多是找不到合適的Widget去實現想要的佈局或者效果,甚至包括css樣式做用於那個Widget,譬如Opacity是一個widget而不是一個css樣式~
因此對於Flutter,咱們仍是要多折騰,多些demo,相似網上不少仿xxxApp等~
對於畫界面,更多的還能夠參看下官網教程:Flutter for Web開發者
Flutter一切基於Widget,搞定widget就比如,搞定英語單詞同樣,單詞、詞組都賊6了還怕英語?
別急別急,借用張晟哥的圖來給你們消消火氣~
因此說,Flutter有一個龐大的組件體系,須要花費很是多的時間去梳理。
!更重要的是:多實踐
原本最後一章是本身寫的一個demo的講解~
惋惜時間評估不許確,漏評估了假期惰性。。。考慮篇幅,後面補上仿XXX的Demo吧~~
Demo 推薦