Flutter開發實戰初級(1)ListView詳解html
Flutter開發實戰 高仿微信(1)首頁github
源碼地址:flutter_wetchatswift
class KYLRootPage extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _RootPageState(); } } class _RootPageState extends State<KYLRootPage> { int _currentIndex = 0; List<Widget> pages = [Scaffold( appBar: AppBar( title: Text('微信'), ), body: Center( child: Text('微信主頁'), ), ), Scaffold( appBar: AppBar( title: Text('通信錄'), ), body: Center( child: Text('通信錄列表'), ), ), Scaffold( appBar: AppBar( title: Text('發現'), ), body: Center( child: Text('發現列表'), ), ), Scaffold( appBar: AppBar( title: Text('我'), ), body: Center( child: Text('個人頁面'), ), ) ]; @override Widget build(BuildContext context) { // TODO: implement build return Container( child: Scaffold( bottomNavigationBar: BottomNavigationBar( onTap: (int index) { _currentIndex = index; }, type: BottomNavigationBarType.fixed, fixedColor: Colors.green, currentIndex: _currentIndex, items: <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.chat), title: Text('微信'), ), BottomNavigationBarItem( icon: Icon(Icons.bookmark), title: Text('通信錄'), ), BottomNavigationBarItem( icon: Icon(Icons.history), title: Text('發現'), ), BottomNavigationBarItem( icon: Icon(Icons.person_outline), title: Text('我'), ), ]), body: pages[_currentIndex], ), ); } } 複製代碼
import 'package:flutter/material.dart'; import 'KYLRootPage.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', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: KYLRootPage(), ); } } 複製代碼
至關因而一個自定義的Button,用來放在BottomNavigationBar上,它實現了Material(Android)和Cupertino(iOS)兩種風格。微信
Scaffold是Root Widget- MaterialApp的腳手架。封裝了Material Design App會用到的AppBar,Drawer,SnackBar,BottomNavigationBar等。BottomNavigationBarType有fixed 和shifting兩種樣式,超過3個纔會有區別,通常爲了體驗一致,咱們會用fixed type。markdown
BottomNavigationBar是一個StatefulWidget,能夠按如下步驟分析這種組件: 1,先看它持有的狀態; 2,看下他的生命週期實現; 3,再仔細分析它的build方法.app
List<AnimationController> _controllers = <AnimationController>[]; List<CurvedAnimation> _animations; // A queue of color splashes currently being animated. final Queue<_Circle> _circles = Queue<_Circle>(); // Last splash circle's color, and the final color of the control after // animation is complete. Color _backgroundColor; 複製代碼
前面三個屬性都和動畫相關,第四個是設背景。 這裏有個疑問:BottomNavigationBar爲何沒有變量標記當前哪一個item選中?less
函數式編程一個原則是要函數儘可能純,currentIndex這個屬性依賴外邊傳入,每次變化從新觸發Render。若是本身維護,則還須要提供一個回調方法供外部調用,返回最新的currentIndex值。ide
// 初始化操做,具體實現再resetState裏,對上面的這些狀態屬性初始化操做 @override //initState裏有個操做比較隱蔽:_controllers[widget.currentIndex].value = 1.0; void initState() { super.initState(); _resetState(); } // 回收資源操做,通常用到動畫都須要的 @override void dispose() { for (AnimationController controller in _controllers) controller.dispose(); for (_Circle circle in _circles) circle.dispose(); super.dispose(); } // 當屬性變化時Flutter系統回調該方法。當item數量變化時直接從新初始化;當index變化,作相應動畫。 @override void didUpdateWidget(BottomNavigationBar oldWidget) { super.didUpdateWidget(oldWidget); // No animated segue if the length of the items list changes. if (widget.items.length != oldWidget.items.length) { _resetState(); return; } if (widget.currentIndex != oldWidget.currentIndex) { switch (widget.type) { case BottomNavigationBarType.fixed: break; case BottomNavigationBarType.shifting: _pushCircle(widget.currentIndex); break; } _controllers[oldWidget.currentIndex].reverse(); _controllers[widget.currentIndex].forward(); } if (_backgroundColor != widget.items[widget.currentIndex].backgroundColor) _backgroundColor = widget.items[widget.currentIndex].backgroundColor; } // 下面分析 @override Widget build(BuildContext context) {} 複製代碼
@override Widget build(BuildContext context) { // debug 檢查 assert(debugCheckHasDirectionality(context)); assert(debugCheckHasMaterialLocalizations(context)); // Labels apply up to _bottomMargin padding. Remainder is media padding. final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - _kBottomMargin, 0.0); // 根據BottomNavigationBarType設背景色,shifting纔會有 Color backgroundColor; switch (widget.type) { case BottomNavigationBarType.fixed: break; case BottomNavigationBarType.shifting: backgroundColor = _backgroundColor; break; } return Semantics( // Semantics用來實現無障礙的 container: true, explicitChildNodes: true, child: Stack( children: <Widget>[ Positioned.fill( child: Material( // Casts shadow. elevation: 8.0, color: backgroundColor, ), ), ConstrainedBox( constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding), child: Stack( children: <Widget>[ Positioned.fill( // 點擊時的圓形類波紋動畫 child: CustomPaint( painter: _RadialPainter( circles: _circles.toList(), textDirection: Directionality.of(context), ), ), ), Material( // Splashes. type: MaterialType.transparency, child: Padding( padding: EdgeInsets.only(bottom: additionalBottomPadding), child: MediaQuery.removePadding( context: context, removeBottom: true, // tiles就是_BottomNavigationTile,裏面放BottomNavigationBarItem child: _createContainer(_createTiles()), )))]))])); }} 複製代碼
Widget _buildIcon() { ... // 構建Icon } Widget _buildFixedLabel() { .... // 騷操做,用矩陣來給文字做動畫,更平滑 // The font size should grow here when active, but because of the way // font rendering works, it doesn't grow smoothly if we just animate // the font size, so we use a transform instead. child: Transform( transform: Matrix4.diagonal3( Vector3.all( Tween<double>( begin: _kInactiveFontSize / _kActiveFontSize, end: 1.0, ).evaluate(animation), ), ), alignment: Alignment.bottomCenter, child: item.title, ), ), ), ); } Widget _buildShiftingLabel() { return Align( ..... // shifting的label是fade動畫,只有當前選中的纔會顯示label child: FadeTransition( alwaysIncludeSemantics: true, opacity: animation, child: DefaultTextStyle.merge( style: const TextStyle( fontSize: _kActiveFontSize, color: Colors.white, ), child: item.title, ), ), ), ); } @override Widget build(BuildContext context) { int size; Widget label; // 生成不一樣的label switch (type) { case BottomNavigationBarType.fixed: size = 1; label = _buildFixedLabel(); break; case BottomNavigationBarType.shifting: size = (flex * 1000.0).round(); label = _buildShiftingLabel(); break; } return Expanded( .... children: <Widget>[ _buildIcon(), label, ], ), ), Semantics( label: indexLabel, } 複製代碼
Container在Flutter中太常見了。官方給出的簡介,是一個結合了繪製(painting)、定位(positioning)以及尺寸(sizing)widget的widget。 能夠得出幾個信息,它是一個組合的widget,內部有繪製widget、定位widget、尺寸widget。後續看到的很多widget,都是經過一些更基礎的widget組合而成的。
最裏層的是child元素; child元素首先會被padding包着; 而後添加額外的constraints限制; 最後添加margin。
首先會繪製transform效果; 接着繪製decoration; 而後繪製child; 最後繪製foregroundDecoration。
Container在沒有子節點(children)的時候,會試圖去變得足夠大。除非constraints是unbounded限制,在這種狀況下,Container會試圖去變得足夠小。 帶子節點的Container,會根據子節點尺寸調節自身尺寸,可是Container構造器中若是包含了width、height以及constraints,則會按照構造器中的參數來進行尺寸的調節。
key:Container惟一標識符,用於查找更新。
alignment:控制child的對齊方式,若是container或者container父節點尺寸大於child的尺寸,這個屬性設置會起做用,有不少種對齊方式。
padding:decoration內部的空白區域,若是有child的話,child位於padding內部。padding與margin的不一樣之處在於,padding是包含在content內,而margin則是外部邊界,設置點擊事件的話,padding區域會響應,而margin區域不會響應。
color:用來設置container背景色,若是foregroundDecoration設置的話,可能會遮蓋color效果。
decoration:繪製在child後面的裝飾,設置了decoration的話,就不能設置color屬性,不然會報錯,此時應該在decoration中進行顏色的設置。
foregroundDecoration:繪製在child前面的裝飾。
width:container的寬度,設置爲double.infinity能夠強制在寬度上撐滿,不設置,則根據child和父節點二者一塊兒佈局。
height:container的高度,設置爲double.infinity能夠強制在高度上撐滿。
constraints:添加到child上額外的約束條件。
margin:圍繞在decoration和child以外的空白區域,不屬於內容區域。
transform:設置container的變換矩陣,類型爲Matrix4。
child:container中的內容widget。
實例:
new Container( constraints: new BoxConstraints.expand( height:Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0, ), decoration: new BoxDecoration( border: new Border.all(width: 2.0, color: Colors.red), color: Colors.grey, borderRadius: new BorderRadius.all(new Radius.circular(20.0)), image: new DecorationImage( image: new NetworkImage('http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0), ), ), padding: const EdgeInsets.all(8.0), alignment: Alignment.center, child: new Text('Hello World', style: Theme.of(context).textTheme.display1.copyWith(color: Colors.black)), transform: new Matrix4.rotationZ(0.3), ) 複製代碼
Container算是目前項目中,最常常用到的一個widget。在實際使用過程當中,筆者在如下狀況會使用到Container,固然並非絕對的,也能夠經過其餘widget來實現。
decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null), 複製代碼
能夠看出,對於顏色的設置,最後都是轉換爲decoration來進行繪製的。若是同時包含decoration和color兩種屬性,則會報錯。
@override Widget build(BuildContext context) { Widget current = child; if (child == null && (constraints == null || !constraints.isTight)) { current = new LimitedBox( maxWidth: 0.0, maxHeight: 0.0, child: new ConstrainedBox(constraints: const BoxConstraints.expand()) ); } if (alignment != null) current = new Align(alignment: alignment, child: current); final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration; if (effectivePadding != null) current = new Padding(padding: effectivePadding, child: current); if (decoration != null) current = new DecoratedBox(decoration: decoration, child: current); if (foregroundDecoration != null) { current = new DecoratedBox( decoration: foregroundDecoration, position: DecorationPosition.foreground, child: current ); } if (constraints != null) current = new ConstrainedBox(constraints: constraints, child: current); if (margin != null) current = new Padding(padding: margin, child: current); if (transform != null) current = new Transform(transform: transform, child: current); return current; } 複製代碼
Container的build函數不長,繪製也是一個線性的判斷的過程,一層一層的包裹着widget,去實現不一樣的樣式。 最裏層的是child,若是爲空或者其餘約束條件,則最裏層包含的爲一個LimitedBox,而後依次是Align、Padding、DecoratedBox、前景DecoratedBox、ConstrainedBox、Padding(實現margin效果)、Transform。 Container的源碼自己並不複雜,複雜的是它的各類佈局表現。咱們謹記住一點,若是內部不設置約束,則按照父節點儘量的擴大,若是內部有約束,則按照內部來。
Scaffold 實現了基本的 Material 佈局。只要是在 Material 中定義了的單個界面顯示的佈局控件元素,均可以使用 Scaffold 來繪製。 提供展現抽屜(drawers,好比:左邊欄)、通知(snack bars) 以及 底部按鈕(bottom sheets)。 咱們能夠將 Scaffold 理解爲一個佈局的容器。能夠在這個容器中繪製咱們的用戶界面。
Scaffold源碼分析
Scaffold 主要的屬性說明
class Scaffold extends StatefulWidget { /// Creates a visual scaffold for material design widgets. const Scaffold({ Key key, this.appBar, //橫向水平佈局,一般顯示在頂部(*) this.body, // 內容(*) this.floatingActionButton, //懸浮按鈕,就是上圖右下角按鈕(*) this.floatingActionButtonLocation, //懸浮按鈕位置 //懸浮按鈕在[floatingActionButtonLocation]出現/消失動畫 this.floatingActionButtonAnimator, //在底部呈現一組button,顯示於[bottomNavigationBar]之上,[body]之下 this.persistentFooterButtons, //一個垂直面板,顯示於左側,初始處於隱藏狀態(*) this.drawer, this.endDrawer, //出現於底部的一系列水平按鈕(*) this.bottomNavigationBar, //底部持久化提示框 this.bottomSheet, //內容背景顏色 this.backgroundColor, //棄用,使用[resizeToAvoidBottomInset] this.resizeToAvoidBottomPadding, //從新計算佈局空間大小 this.resizeToAvoidBottomInset, //是否顯示到底部,默認爲true將顯示到頂部狀態欄 this.primary = true, // this.drawerDragStartBehavior = DragStartBehavior.down, }) : assert(primary != null), assert(drawerDragStartBehavior != null), super(key: key); 複製代碼
關於 Scaffold.of 函數的說明:docs.flutter.io/flutter/mat…
顯示 snackbar 或者 bottom sheet 的時候,須要使用當前的 BuildContext 參數調用 Scaffold.of 函數來獲取 ScaffoldState 對象,而後使用 ScaffoldState.showSnackBar 和 ScaffoldState.showBottomSheet 函數來顯示。
來自官方源碼上面的例子。使用 SnackBar 的寫法。
@override Widget build(BuildContext context) { return new RaisedButton( child: new Text('SHOW A SNACKBAR'), onPressed: () { Scaffold.of(context).showSnackBar(new SnackBar( content: new Text('Hello!'), )); }, ); } 複製代碼
當 Scaffold 其實是在同一個構建函數中建立時,構建函數的 BuildContext 參數不能用於查找 Scaffold(由於它位於返回的小部件的「上方」)。 由於在源碼中 使用的是 return new Scaffold(app:xxxx),在這種狀況下面,經過在 Scaffold 中使用一個 Builder 來提供一個新的 BuildContext:
@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Demo') ), body: new Builder( // Create an inner BuildContext so that the onPressed methods // can refer to the Scaffold with Scaffold.of(). builder: (BuildContext context) { return new Center( child: new RaisedButton( child: new Text('SHOW A SNACKBAR'), onPressed: () { Scaffold.of(context).showSnackBar(new SnackBar( content: new Text('Hello!'), )); }, ), ); }, ), ); } 複製代碼
按照官方的說法,能夠將咱們的構建函數拆分到多個 Widgets中。分別引入新的 BuildContext 來獲取 Scaffold.