• 初级基础系列

Flutter开发实战初级(1)ListView详解

Flutter开发实战初级(2)布局详解

  • 项目实战系列

Flutter开发实战 高仿微信(1)主页

Flutter开发实战 高仿微信(2)发现页

Flutter开发实战 高仿微信(一)主页

源码地址T n 2 $ y g d:flutter_wetchat

1. 开发HomePage页

  1. 运行效果:
    Flutter开发实战  高仿微信(1)主页
  2. 功能介绍
  3. 代码解r N W Q % N p – {
  • KYLRootPage是根页面
class KYLRootPage exT V V t a j p 2 ;tends StatefulWidget {
@override
Statea ? 8 &&ltq L ; r };Sta. { t M O } ]tefulWidget> createS4 V _tate() {
// TODO: implement createU g -State
return _RootPageState(& V 1 E);
}
}
class _RootPageState extends State<KYLRootPage> {
int _currentIndex = 0;
List<Widget> pages = [Scaffold(
appBar: App7 q ! ]Bar(
title: Text('微信'),
),
body: Center(
child: Text('微信主页'),
),
),
SG % ^caffold(
apT =  i z ; UpBar: AppBap G @ } y %r(
title: Text('通讯录'),
),
body: Center(
child: Text('通讯录V j @列表'),
),
),
Scaffold(
appBar: AppBar(
title: Text('发现'),
),
body: Center(
child: Text('发现列表'),
),
),
Scaffold(
appBar: A} K / d ? dppBar(
title: Text('我'),
),
body: Center(
child: Text('我的页面'),
),
)
];
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
cs I 6 O ~hild: Scaffold(
bottomNavigaO 1 B $ ^ )tionBar: BottomNavigationBar(
onTap: (int index) {
_currentIndex = indC / -ex;
},
type: BottomNavigationBarType.fixed,
fixedColor: Colors.green,
currentIndex: _currentIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.chat),
title: Text('微信')F u J c o A _ o,
),
BottomNav8 1 3 ^ a kigationBarItem(
icon: Icon(Icons.bookmark),
title: Text('通讯录'),
),
Bot_ O K m Y V -tomNavigationBarItem(
icon: Icon(Icons.history),
title: Text('发现f J D o J'),
),
BottomNavigationBarItm V z * P 3 ,em(
icon_ ; s 8 W: Icon(Icons.person_outline),
title: Text('我'),
),
]),
body: pages[_currentIndex],
),
);
}
}
  • main.dart
import 'package:flutter/material.dart';
import 'KYLRootPage.d d % C 0 l ?art';
void main() => runApp(MyApp());
claH p T }ss MyApp extends StatelessWidget {
//@ G G w This widget is the rog n m z F 3 I `ot of your al J 8 % * v I ?pplication.
@override
Widget build(Buid  ^ rldContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// TX ~ a g (his is the theme of your application.
//
// Try running your application with "flutter run". You'll see t7 m Z z 1 Q T m Xhe
// application has a blue toolbar. Then, without quitting the app% l n * 0, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r% | n _ u  I -" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flq   l L / N k 1utter IDE).
/d 3 H 7 O h D m r/ Notice that the counter didn't reset bacb E , U R $ 7 )k( . 8 P Y =  to zero; the applii L 3 { V n Q B (cation
//q l N P - ! Q is not rest| W ,arted.
primarySwatch: Colors.blue,
),
home: KYLRm Z b e ` r 3 sootPage(),
);
}
}

2. 用到的知识点说明

2.1 BottomNavigationBar

Flutter开发实战  高仿微信(1)主页

恰当所以一个自定义的Bui / i Ltton,用来放在BottomNaviY M 1gationBar上,它完结了Material(AndroR # / . M a l Mid)和Cupertino(iO$ ~ D b k ^ xS)两种风格。

Flutter开发实战  高仿微信(1)主页

Scaffold是Root Widget- Materig ] W c 8 h I y |alApp的脚手架。封装了Material Design App会用到的AppBar,Drawer,SnackBar,BottomNavigationBar等。BottomNavigationBa M l / b W x ) crType有fixed 和] b [ z s T sshifting两种样式,逾越3个才会有I L l 5 0区别,一般为了领会共p I R f G同,我们会用fixed type。

BottomNavigationBar是一个StatefulWidget,可以按以下进程分析这种组件:
1,先看它持有的情况;
2,看下他的生命周期完结;
3,再细心分析它的bJ 1 A f L % xuild方法.

  • 持有情况
List<AnimationController> _controlL ] 2 ilers = <AnimationController>[];
List<CurvedAnimation> _animations;
// A queue of~ E [ b ! R - color splashes currently being animated.
final Queue<_Circle> _circles = Queue<_Circle>k K , F z();
// Last splash ci{ P W j k 5 . c lrcle x 7 's color, and the final color of the control after
//X 0 H - O X x H 9 animation is complete.
Color _backgroundColor;

前面三个特色都和动画相关,第四个是设布景。
这里有个疑问:BottomNa B f qvigationBar为什么没有变量符号其时哪j t g L o *个item选中?

函数式编程一个原则是要函数尽W _ ; q – 2量纯,currentIndex这个特色依靠外边传入,每次改变从头$ * T v触发Render。假设自己维护,则还需求供给一个! U k c 0 ~ } S回调方法G 5 ; G供外~ _ ^ ] =部调n m I + 1 Y ; I I用,回来最新的currentIndex值。

  • 生命周期方法
// 初始化操作,具体完结g 4 , ! e - & j再r W FesetState里,对上面的这些情况特u O & p ^ $ U ( s点初始化操作c z 7 O M j t $ ~
@override
//initState里有个操作比较荫蔽:_controllers[widget.cu B K F 6 Wrren, @ @ ` s H } ZtIndex].value = 1.0;
void initState() {
super.initState();
_resetState. r = 1 O x 7();
}
// 收回资源操作,一般用到动画都需求的
@override
void dispose() {
for (AnimationController controller iU 4 R a ^ Un _controllers)X a | _ { e [ k E
controller.dispose();
fy ; @or (_Circle circle in _circles)
circle.dispose();
s- v ]uper.dispose();
}
// 当特色改变时Flutter系统回调该方法。当item数量改变时直接从头初始化;当index改变,做相应动画。
@override
void didUpdw G lateWidget(BottomNavigation5 M ( ? tBar oldWidget) {
super.didUpdateWidget(oldWidget);
// No animate~ I a  S S B Z vd segue if they + O K L x length of9 6 y the items list changes.
if (widget.items.length !=A K [ p * ( d @ oldWidget.items.length) {
_resetState()} J  t 6 { ` 9 A;
return;
}
if (widget.currentIndex != oldWidget.currentIndex) {
switch (widget.type) {
case BottomNavigationBarType.fixed:
break;
case BottomNavigationBarType.shifting:
_pushCircle(widget.currentInj = R ] t W E # idex);
break;
}
_controllers[oldWidget.currentIndex].reverse();
_controllers[widget.! a 0 ~ $ dcurrentIndex].forward();
}
if (_backgro a , % % c X iundColor != widget.items[widget.curZ & ; M x 3 orentIndex].backgroundColor)
_backgroundColor = widget.items[widgetK S } } }.currentIndex].backgroundColor;
}
// 下面分析
@override
Widget build(BuildContext context) {}
  • 分析build方法
@override
Widget build(BuildContext context) 7 8 / { ^ R b {
// debug 查看
asse% q $ H ~rt(debugCheckHasDirectionality(context));
assert(debugCheckHasMaW 7 ` = &terialLocalization9 u _s(context));# ^ 1 H
// Labels apply up to _bottomMargin padding. RemainderK 8 { r  is meG 5 V $ #dia pU 1 y ! ^ + F E zaddu  K - c 9 P m ming.
final double additiona I 1 n t 6 vlBottomPadding = math.max(MediaQuery.of(context).padding.bottom - _kBottomMargin, 0.0);
// 根据BottomNavigationBarType设布风光,shifting才会有
Color backgroundColor4 2 8 F j g r i 6;
switch (widget.type) {
case BottomNavigationBarType.fixedJ @ ) r f _ } w 2:
break;
case BottoI h 3 4 @ z y ^mNavigationBarT6 Y S i - K : Dype.shifting:
backgroundColor =j h t 7 H _backgroundColor;
break;
}
r# b J , {eturn Semantics( // Semantics用来完结无障碍的
container: true,
explicitChildNodes: true,
child: Stack(
children: <Widget>[
Positioned.fill(
child: Material( // Casts shadow.
eli r Y H k zevation: 8.0,
color: backgroundColor,
),
),
ConstrainedBox(
constraints: Box; 6 ^ 6 ) V }Constraints(minHeightv 1 M 3: kBottomNn B ] q +avigationBarHeight + additionalBottomPadding),
child: Stack(
children: <Widget>[
Positioned.fill(  // 点击时的圆形类波K z l纹动画
child: CustomPaint(
painter: _RadialPainv A J zter(
circles: _circles.toList(),
textDirection: Directionality.of(context),
),
),B - d f % 0
),
Material( // Splashes.
type: MaterialType.G e & c u S ^transparen^ 5 ; E , ~ Z Icy,
child: Padding(
padding: EdgeInsets.only(boT I + : 1 s / 4ttom: add^ X g %itionalBottomPadding),
child: MediaQuery.removePadding(
context: contex~ ] t ! / ( D V Kt,
removeBottom: true,
// til` ? | 8  kes便是_BottomNavigationTile,里边放BottomNavigationB: q k 3 v K W N +arItem
child: _createContainer(_createTiles()),
)))]))]));
}}
  • _BottomNavigationTile看下
Widget _buildIcon() {
...
// 构建Icon
}w W - _ q r ( ! 6
Widw 7 r rget _buildFixedLabel() {
....
// 骚操作,用矩阵来给文字作动画,更滑润
// The font size should grow here when active, butS = ) M z because of the way
// font re@ . Xndering works, it doesn't grow smoothly if we just and x Eimate
// the font size, so we use a tri G M sansform instead.
child: Transform(
transform: Matrix4.diagonal3(
Ve! - S pctor3.all(
Tween<double>(
begin: _kInactivf p ` b 8 u {eFontSize / _kActiveFontSize,
end: 1.0,
).evaluate(animation),
),
),
alignment: Alignmx O @ Qent.bottomCenter,
child: item.title,
),
),
),
);
}
Widget _bum 4 @ _ildShiftingLabel() {
return Align(
.....
//! R i C T Y Z shifting的label是fade动画,只要其时选中的才会闪现label
chB ?  r $ Tild: FadeTransition(
alwaysIncludeSemantics: true,
opacity: animation,
child: DefaultTextSz z & ztyle.merge(
style: const TextStyle(
fontSize: _kActivt 6  & & 9 veFontSize,
color: Colors.white,
),
child: i: R M ` X Ytem.title,
),
),
),
);
}
@override
Widget build(BuildContext context) {
in5 r 6t size;
Widget label;
// 生成不同的label
switch (type) {
case BJ ^ W H G Y U LottomNavigationBarType.fixed:
size = 1;
label = _buildFixedLabel();
break;
case BottomNavigationBu V zarType.shifting:
size = (flex * 1000.0).round();
label = _buildShiftingL9 _ U ( C j 6 Pabel();
break;
}
return Expanded(
....
children: <Widget>[
_buildIcon(),
label,
],
),
),
Semantics(
label: indexLabel,
}

2.2 Container

2.2.1. 简介

Container在Flutter中太常见了。官方给出的简G n ` H 7 = F介,是一个结合了制造(painting)、定位(positioning)以及标准(sizing)widget的widget。
可以得出几个信y M : H息,它是一个组合的widge~ = [ N H J :t,内部有制造widget、定位widget、标准widget。后续看到的不少widget,都是经过一些更基础的widget组合而成的。

2.2.2. 组成
  1. Container的组成如下:

最里层的是child元素;
child元素首要会被padding包着;
然后添加额定的constraints捆绑;
终究添加, U , Qmargin。

  1. Cont@ ! d B sainer的制造的进程如下:

首要会制造transform效果;
接着制造decoration;
然后制造child;
终究制造foregroundDecoration。

  1. Container本身标准的调度分两种情况:

Container在没有子节点(children)的时分,会企图去变得足够大。除非constraints是unboundedH V ^ 8 U R l 0 –捆绑,在这种+ g g 3情况下,Container会企图去变得足够小。
带子节点的Container,会根据子节点标准调度本身标准,但是Container结构器中假设包含了width、heightF B p @ W 7 J * M以及constraint~ 1 G L U zs,则会按照结构器中的参数来进行标准的调度。

2.2.3. Conta! v F 9 +iner的特色
  • key:Container仅有标识符,用于查找更新。

  • alignment:控制child的对齐方法,假设container或许container父节点标准大于child的标准,这个特色设置会起效果,有很多种对齐方法。

  • padding:decoration内部的空白L + ] @ p区域,假设有child的话,chA V X k + aild坐落paddii 0 6 eng内部。padding与d 2 + lmargin的不同之处在于5 ! x },padding是包含在contg $ $ n ]en[ 1 M n m ;t内,而marginc j $ o & b i则是外部边界,设置点击事情的话,padding区5 4 , K r i 6域会呼应,而margin区域不会呼应。

  • color:用来设置container布风光,假设foregroun/ W p 6dDecoration设置的话,可能会隐秘color效果。

  • decoration:制造在child后边的装饰,设置了dei k H C N B fcoration的话,就不能设置color特色,否则会报错,此刻应该在decoration中进行颜色的设置。

  • foregroundDecoration:制造在child前面的装饰。

  • width:container的宽度,设置为double.infinity可以强制在宽度上撑满,不设置,则根据child和父节点两者一同布局。

  • height:container的高度,设置为double.infinity可以强制在高度上撑满。

  • constrain5 4 v ets:添加到child上[ ( E % a H额定的捆绑条件。

  • margin:围绕在decoration和child之外的空白区域,不属于内容区域。

  • transform:设置container的变换矩阵,类型为Matrix4。

  • child:container中的内容 f ; t ~ S B ? ZwidgD g / , xet。

实例:

new Container(
constraint+ l c g v =s: new BoxConstraints.expand(
height:Theme.o9 r { N * @ Z ` kf(context).textTheme.display1.fontSize * 1.1 + 200.0,
),t , 6 S X P y
decoration:? , J ; E ; . 1 new BoxDecoration(
border: new Bor% 2 |der.all(width:E 4 S d C : i 2.0, co{ T Klor: Colors.red),
color: Colors.grey,
borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
image: new DG K y . 1 ! K *ecorationIp f e g 9 S lmage(
image: new NetworkImage('http://h.hiphotu ( 8 x 1 t 6os.baidu.com/zhi` B ;dao/b `  Owh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
),
),
padding: const EdgeInset( { - h 9 J g j zs.all(8.0),
alignment: Alignment.center,
child: new Text('Hellu W % 8 y r d qo World',
style: Theme.of(context).textTheme.display1.copyWith(color: Colors.black)),
transform: new Matrix4.rotationZ(0.3),
)
2.2.4. Container运用

Container算是现在项目中,最经常用u j F到的一个wiY O odget。在{ , & J实际运用进程中,笔者在以下情况会运用到Container,当然并不是肯定的,也可以经过其他widC _ Z _ 9 B ^ L .get来完结。

  1. 需求设置距离(这种情况下,假设只是单纯的距离,也可以经过Padding来完结)E 7 x e r –
  2. 需求设置布风光;
  3. 需求设置圆角或许边J _ , , [ + { M l框的时分(ClipRRect也可以完结圆角效果);
  4. 需求对齐(Al9 d Z : : @ A Xign也可以完结);
  5. 需求设置布景图片的时分(也可以运用Stack完结)。
2.2.5. Container源码分析
decoration = deco) C $ration ?? (color != null ? new BoxDecoration(color: color) : null),

可以看出,关于颜色的设置,终究都是转换为decoration来进行制造的。假设一起包含decoration和coU O ilo2 @ S d _r两种特色,则会报错。

@overridf C W x ; 2 5e
Widget build(BuildContext context2 V u) {
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:~ r % 1 e M J k O current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDec! [ [ [ 2 j Moration;
if (effectR ( U t A ?ivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
if (decoration != null)
current = new DeY r 9 6 |coratedBox(decoration: decoration, child: current);
if (foregrounR 6 = : l P 0 OdDecoration != null) {
current = newx R t F P A K ? . DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: currenT ] z P # 8 Z gt
);
}
if ({ - * a t Iconstraints != null)
current = new Con# a q j g 7 Q a sstrainedBox(constraints: constraints, child: cub C Q v f 2rrent);
if (margin != null)
currenh # #t = new Padding(padding: margin, child: current);
if (transform != null)
current = new Trans5 O x e h D Jform(transform: transform, child: current);
return current;
}

Container的build_ P Q函数不长,制造也是一个线性的判别的进程,一层一层的包裹着8 P N nwidget,去完结不同的样式。
最里层的是child,假设为空或许其他捆绑条件,则最里层包含的为一个LimitedBox,然后依次是Align、Padding、DecoratedBox、远景DecoratedBox、ConstrainedBox、Padding(完结margin效果)、Transform。
Container的源码本身并不杂乱,杂乱的是它的各种布局表现。我们谨记住一点,假设内部不设置捆绑,则按照父节点尽可能的扩# H M 大,假设内部有捆绑,则按照内部来。

2.3 Scaffold

Scafj B q & – k x t Efold 完结了根` M = G G * 4本的 Material 布局。只要是在 Material 中: } s C 6 x w定义了的单个界面闪现的布局控件元素,都可以运用 ScaffoJ 0 0 # ` NldN q T , Q 来制造。
供给展示抽屉(drawers,比如:左边栏)R h X、告诉(snack bars) 以及 底部按钮(q B Q 2 * = Zbottom sheets)。
我们可以将 Scaffold 理解为一个布局的容器。可以在这个容器中制造我们的用户界面。

  1. Scaffold源码分析

    Flutter开发实战  高仿微信(1)主页
  2. Scaffold 首要的特色说明

  • appBar:闪现在界面顶部的一个 AppBar
    相关衔接:flutterchina.club/catalog/sam…
  • body:其时界{ L [面所闪现的首要内容
  • floatingActionButton: 在 Material 中定义的一个功, j e u 7 c , X 3能按钮。R P O
  • persistentFooterButtons:固定在下方闪现的按钮。material.goog` H L + 2 ? K +le.com/components/…
  • drawer:侧边栏控件
  • bottomNG c { A 8avigationBar:闪现在底部的导航栏按钮栏。可以查看文档:Flutter学习之制造底部菜单导航
  • backgroundColor:布风光彩
  • resizeToAvoidBottomPadd[ q F B K 5 *ing: 控制界面内容 bof J W ! 6 F Idy
    是否从头布局来避免底部被覆盖了,比如当键盘闪现的时分,从头布局避免被键盘盖住内容。默认值为 true。
  1. 代码示例
class Scaffold extends. c b StatefulWidget {
/// CreaH 3 ] f 8tes a visual scaffold for material design widgets.
const Scaffold({
Key key,
this.appBar, //横向水平布局,一般闪现在顶部(*)
this.body, // 内容(*)
this.fh f V {loatingActionButton, //悬浮按钮,便是上图右下角按钮(*)
this.floatingActionButtonLocation, //悬浮按钮位置
//悬浮按钮在[floatingActionButtonLocation]出现/消失动画
this.floatingActionButtonAnimator,
//: Y x r p m z t )在底部出现一组button,闪现于[bottomNavigationBar]之上,[body]之下
this.persistentFooterButtons,
//一个笔直面板,闪现于左边,初始处于躲藏情况(*)
thp W { is.drawel ` 1 j X [ Er,
this.endDrawer,
//出现于底部的一系列水平按钮(*)
this.bottomNavigationBar,
//底部耐久化提示框
this.bottomSheet,
//内容布风光彩
this.backgroundb Q I ,ColU z a W Dor,
//弃用,运用[resizeToAvoidBottomInset]
this.resizeToAvoidg  ) e bBottomPadding,
//从头核算布局空间巨细
this.resizeToAvoidBottomInset,
//是否闪现到底部,默以为true将闪现到顶部情况栏
this.primary = true,
//
this.drawerDragStartBehavior = DragStartBe } p 0 , ! !havior.down,
}) : assert(p$ h Primary !Z ? 6 | Z 7 .= null),
assert(drawerD E & 3ragStaM g @ t 9rtBehavio0 * ! J k 6 ) rr != null),
sl ^ K n ) % o {upen U s - ! l r 1r(key: key);
  1. Scaffold.of 运用说明

关于 Scaffold.of 函数的说明:docs.flutter.io/flutter/mat…

闪现 snackbar 或许 bottom sheet 的时分,需求运用其时的 BuildContext 参数调用 Scaffold.of 函数来获取 Scaffom ^ s }ldState 对象,然后运用 ScaffoldSY ) k M x Wtate.showSnackBar 和 ScaffoldState.shoj { 1 t + ywBottomSheet 函数来闪现。

来自官方源码上面的例子。运用 SnackBar 的写法。

@override
Widget build(BuildConte9 u = ? Y R p a Mxt context) {
return new RaisedButton(
child: new Text('SHOW A SNR c t 8 DACKBAR'),
on0 2 f !PressedA 6 3 / v $ o b @: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text('Hello!'),
));
},
);
}

当 Scaffold 实际上是在同一个构建函数中创立时,构建函数的 BuildContext 参数不能用于查找 Scaffold(因为它# 6 [ X坐落回来的小部件的“上方”)。 因为在源码中 运用的是 return new Scaffold(app:xxxx) V ) J,在这种情况下面G [ r,经过在 Scaffold 中运用一个 Builder 来供给一个新的 Buf ! d ) , zilp U c – = R p DdContext:

@override
Widget build(BuildCoX h | P zntext context) {
return new Scaffold(
appBar: new AppBar(
titu x 5 J + Jle: new Tex; U : @ p - Ct('Demo')
),
body: new Builder(
// CY 7 O A /reate an inner BuildContext so that the onPressedw ! 1 ? Z 2 P N methods
// can refer to the Scaffold with Scaffold.of().
builder: (BS ? 2 s ,uildContext conte% d ! ) 3 ; ]xt) {
return new Center(
child: new RaisedButton(
child: new Tex~ 2 { r ; rt('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(new SnackBaM 9 o C % Zr(
content: new Text('Hello!'),
));D K n B
},
),
);
},
),
);
}# / * + } Y f 0 ?

按照官方的说法,可以将我们的构建函数拆分到多个 WiD 8 8 M T ^ Ydgets中。分别引进新的 BuildContext 来获取 Scaffold.