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.