1. Flutter简介
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。
Flutter可以与现有的代码一起工作,是完全免费、开源的,也是构建未来的Google Fuchsia应用的主要方式。
Flutter组件采用现代响应式框架构建,这是从React中获得的灵感,中心思想是用组件(widget)构建你的UI。
组件描述了在给定其当前配置和状态时他们显示的样子。
当组件状态改变,组件会重构它的描述(description),Flutter会对比之前的描述, 以确定底层渲染树从当前状态转换到下一个状态所需要的最小更改。
2. Flutter及Widget重要概念集合
1. Widget
Flutter中的widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于应用主题数据传递的Theme等等;而原生开发中的控件通常只是指UI元素。
2. Common layout widgets
Two categories:
1⃣️standard widgets from the widgets library
- Container: Adds padding, margins, borders, background color, or other decorations to a widget.
- GridView: Lays widgets out as a scrollable grid.
- ListView: Lays widgets out as a scrollable list.
- Stack: Overlaps a widget on top of another.
2⃣️specialized widgets from the Material library
- Card: Organizes related info into a box with rounded corners and a drop shadow.
- ListTile: Organizes up to 3 lines of text, and optional leading and trailing icons, into a row.
3. Text
1⃣️textAlign(文本的对齐方式;对齐的参考系是Text widget本身;只有Text宽度大于文本内容长度时指定此属性才有意义);
2⃣️maxLines、overflow(指定文本显示的最大行数,默认情况下,文本是自动折行的,如果指定此参数,则文本最多不会超过指定的行;有多余的文本时,可以通过overflow来指定截断方式,默认是直接截断);
3⃣️extScaleFactor(代表文本相对于当前字体大小的缩放因子);
4⃣️https://flutter.dev/docs/development/ui/layout
以上链接,直接跟着官方教程认真上手;
5⃣️How Widgets Build UI Front Page
4. Center、Container
1⃣️A child property if they take a single child – for example, Center or Container;
Center(
child: Text('Hello World'),
),
//Center=>child=>只能有一个widget树嵌套;
5. Row、Column、ListView、Stack
1⃣️A children property if they take a list of widgets – for example, Row, Column, ListView, or Stack;
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
//children要用[]括号,child只能用()
//uses Image.asset to display the images. For more information, see this example’s pubspec.yaml file, or Adding Assets and Images in Flutter.
//You don’t need to do this if you’re referencing online images using Image.network.
2⃣️Row、Column —— MainAxisAlignment / CrossAxisAlignment:
校准;
The MainAxisAlignment and CrossAxisAlignment classes offer a variety of constants for controlling alignment.
MainAxisAlignment.spaceEvenly
表示两端对齐校准,即“as much space along its main axis as possible”;
CrossAxisAlignment.center
表示cross Axis居中;
MainAxisSize: MainAxisSize.min
表示Axis的尺寸保持最小,即插入图片的Axis,有利于“pack the children closely together”(组合多个image);
3⃣️Nesting rows and columns(行列嵌套)
行1部分(e.g. 评分):
var stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(...),
...
],
); //icon definition
final ratings = Container(
padding: (...),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars, // here
Text(...),
),
],
),
);
行2部分(e.g. icon展示):
final descTextStyle = TextStyle(
...
); // line 2 icon text's style
// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
final iconList = DefaultTextStyle.merge(
style: descTextStyle, //here
child: Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(...),
Column(...),
Column(...),
],
),
),
);
嵌套部分:
final leftColumn = Container(
padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(
children: [
...
ratings,
iconList,
],
),
);
整体嵌套在body部分:
body: Center(
child: Container(
margin: EdgeInsets.fromLTRB(0, 40, 0, 30),
height: 600,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 440,
child: leftColumn, // here
),
mainImage,
],
),
),
),
),
6. Expanded、
1⃣️利用 Expanded Widget 校正 image.asset 中的图片尺寸问题;
Expanded(
child: Image.asset('images/pic1.jpg'),
),
2⃣️利用 Expanded Widget 中的 flex 呈现你想要的图片效果;
The default flex factor is 1;
你可以设置你想要的flex数值;
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
3. 环境配置
https://flutterchina.club/using-ide/
以上链接,直接跟着提示一步一步来就可以了,毕竟官方网站出的教程;
4. The First Flutter App
1. main.dart
To know how it works, you can code like this.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return new MaterialApp(
title: 'This is whole title',
//Where does it appear?
home: new Scaffold(
appBar: new AppBar(
title: const Text('This is appBar title'),
),
body: const Center(
child: const Text('This is body\'s child'),
),
),
);
}
}
1⃣️return new MaterialApp
表明我们所创建的是一个Material Design风格的应用;Material 是一种移动端和网页端通用的视觉设计语言,Flutter 提供了丰富的 Material风格的widgets;
2⃣️主函数(main)使用了 (=>) 符号,这是 Dart 中单行函数或方法的简写,即函数的声明;
3⃣️extends StatelessWidget
表明该应用程序继承了StatelessWidget,这将会使应用本身也成为一个 widget(即类比Java中父类与子类之间关系);在 Flutter 中,大多数东西都是 widget,包括对齐 (alignment)、填充 (padding) 和布局 (layout);
4⃣️home: new Scaffold
中,Scaffold 是 Material library 中提供的一个 widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性;widget 树可以很复杂;
5⃣️一个 widget 的主要工作是提供一个 build() 方法,来描述如何根据其他较低级别的 widgets来显示自己;
6⃣️body: const Center(child: const Text('This is body\'s child'),),
中的 body 的 widget 树中包含了一个 Center widget,Center widget 又包含一个 Text 子 widget,Center widget 可以将其子 widget 树对其到屏幕中心;
2. Add Package
1⃣️添加依赖包(pubspec.yaml–>package get);
2⃣️引用依赖包(import);
3⃣️使用依赖包中的方法(english_word–>WordPair.random()–>asPascalCase/asLowerCase/asUpperCase);
3. Create Stateful Widget
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';//add this new line--step2
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
//final wordPair = new WordPair.random();
// add this new line--step2
//remove——step3
return new MaterialApp(
title: 'This is whole title',
//Where does it appear?
home: new Scaffold(
appBar: new AppBar(
title: const Text('This is appBar title'),
),
body: new Center(//静态Center 常量widget树改为了新widget树实例
//child: const Text('This is body\'s child'),
//原本是使用const text widget树显示生成text方式
//child: new Text(wordPair.asPascalCase),
//新widget树实例,asPascalCase__大驼峰式命名法--step2
child: new RandomWords(),
//利用state类中的build方法来生成随机文本--step3
),
),
);
}
}
class RandomWords extends StatefulWidget{
@override
RandomWordsState createState() => new RandomWordsState();
}//step3
class RandomWordsState extends State<RandomWords>{
@override
Widget build(BuildContext context){
final WordPair wordPair = new WordPair.random();
return new Text(wordPair.asPascalCase);
}
}//step3
1⃣️stateless widget是不可变的,其所有属性的类型都是final;
2⃣️ stateful widget的状态则可以在生命周期中发生变化;
3⃣️实现一个stateful widget 至少需要两个类:一个 StatefulWidget 类;一个 State 类;
4⃣️StatefulWidget 类本身是不变的,且State 类在 widget 生命周期中始终存在;
5⃣️State<RandomWords>
的声明,表示我们正在使用专门用于 RandomWords 的 State 泛型类;应用的大部分逻辑和状态都在这里:它会维护 RandomWords 控件的状态;而 State 这个泛型类会保存代码生成的单词对,这个单词对列表会随着用户滑动而无限增长,另外还会保存用户喜爱的单词对(第二部分),也即当用户点击爱心图标的时候会从喜爱的列表中添加或者移除当前单词对;
6⃣️RandomWordsState
继承自RandomWords
;
7⃣️RandomWordsState createState() => new RandomWordsState();
到Widget build(BuildContext context)
的变化表明,状态类中必须添加build 方法,一般build 方法创建方式为Widget build(BuildContext context){}
;
8⃣️注意方法里的text 不要手快打成了 test ;要注意写的是 word 还是 words ;应用大驼峰式命名法;
4. Create ListView
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';//add this new line;step2
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
//final wordPair = new WordPair.random();
// add this new line;step2
//remove;step3
return new MaterialApp(
title: 'This is whole title',
//Where does it appear?
/*
home: new Scaffold(
//使用 Scaffold 类实现基础的 Material Design 布局;step4
appBar: new AppBar(
title: const Text('This is appBar title'),
),
body: new Center(//静态Center常量widget树,改为了new widget树对象实例;step2
//child: const Text('This is body\'s child')
//原本是使用const text widget树显示生成text方式;step1
//child: new Text(wordPair.asPascalCase)
//新widget树实例,asPascalCase大驼峰式命名法;step2
child: new RandomWords(),
//利用state类中的build方法来生成随机文本;step3
),
),
*/
home: new RandomWords(),
//用RandomWords()=>build()=>Scaffold()替换原本的一长串Scaffold();step4
);
}
}
class RandomWords extends StatefulWidget{
@override
RandomWordsState createState() => new RandomWordsState();
}//step3
class RandomWordsState extends State<RandomWords>{
final List<WordPair> _suggestion = <WordPair>[];
//List类型的suggestion变量,初始化为空列表,其中的内容为单词对;step4
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
//TextStyle类型的biggerFont变量,初始化为静态的TextStyle变量,其中的fontSize属性值为18.0;step4
//在 Dart 语言中,使用下划线为前缀标识的变量或函数,会强制其变为所在类的私有变量或函数;step4
@override
Widget build(BuildContext context){
//RandomWordsState=>更新RandomWords=>更新MyApp中的child中的RandomWords()=>更新生成方式
/*
final WordPair wordPair = new WordPair.random();
return new Text(wordPair.asPascalCase);
//减少耦合,与原方法一样用于生成单词对,一样实现大驼峰式命名法;step3
*/
return new Scaffold(
appBar: new AppBar(
title: new Text('Text from RWS\'title'),
),
body: _buildSuggestions(),
);
}//使用_buildSuggestions方法生成text;step4
Widget _buildSuggestions(){
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext _context, int i){
//BuildContext类型的context私有变量以及int型的i变量;step4
//在每一列之前,添加一个1像素高的分隔线widget;step4
if(i.isOdd){
return new Divider();
//新的方法前一定要用new声明;step4
//偶数行时,单词对将被添加一个 ListTile row;step4
}
final int index = i ~/ 2;//即 (int)(i/2);step4
if(index >= _suggestion.length){
//如果滑到了最底部,此时分割线的索引大于等于了suggestion List中的单词对数目,则继续生成单词对;step4
_suggestion.addAll(generateWordPairs().take(10));
//'.addAll()'函数表示向suggestion表中添加元素;step4
//'generateWordPairs()'函数表示创建单词对;step4
//'take(int i)'函数表示创建个数;step4
}
return _buildRow(_suggestion[index]);
//将每个单词对都经过buildRow的处理后再输出;step4
}
);
}
Widget _buildRow(WordPair wordPair){
return new ListTile(
title: new Text(
wordPair.asPascalCase,
//生成的单词对要进行大驼峰式命名法美化;step4
style: _biggerFont,
//style属性的值设为之前声明的biggerFont变量改变字号大小;step4
),
);
}
}//step3
1⃣️首先,扩展(继承)RandomWordsState
类,以生成并显示单词对列表;当用户滚动时,ListView 中显示的列表将无限增长;ListView 的 builder 工厂构造函数允许您按需建立一个懒加载的列表视图;
2⃣️Dart 中使用下划线前缀标识变量,会将该变量强制变为当前类中的私有变量;
3⃣️每一个类中必须有一个@override
的标识方法(个人理解,还没来得及看详细的Dart书籍);
4⃣️ListView 类提供了一个 builder 属性;
5⃣️itemBuilder 是一个匿名回调函数,可以接受两个参数:BuildContext 以及行迭代器 i;迭代器从 0 开始,每调用一次该函数,i 就会自增 1 ,对于每个suggestion中创建的单词对都会执行一次;该模型允许单词对列表在用户滚动时无限增长;
5. Review
1⃣️了解整个代码功能的迭代过程;
2⃣️了解Dart语言的特性;
3⃣️了解常用的函数、变量、布局等;
5. Beatify Your First App
1. add icon
trailing: Icon(
alreadySaved ? Icons.star : Icons.star_border,
//if语句三段式,按照bool类型值来判断是否改变icon绘图;step5
color: alreadySaved ? Colors.yellow : null,
//if语句三段式,按照bool类型值来判断是否改变颜色;step5
),
1⃣️在_buildRow中增加trailing方法,调用Icon()添加相应的icon;
2⃣️同时用if判等来为之后的交互UI变化做准备;
2. add interaction
onTap: (){
//增加一个onTap方法,在方法中调用setState() + if判断 来改变交互状态;step6
setState(() {
if(alreadySaved){
_saved.remove(wordPair);
}
else{
_saved.add(wordPair);
}
//setState()中用if语句来判断用户行为,从而改变_saved List中的值
});
1⃣️在_buildRow中增加onTap方法,在方法中调用setState() + if判断 来改变交互状态;
2⃣️setState()中用if语句来判断用户行为,从而改变_saved List中的值;
3. route
_pushSaved方法:
void _pushSaved(){
Navigator.of(context).push(
//表示添加在导航栏部分添加 Navigator.push 调用,实现路由入栈(以后路由入栈均指推入到导航管理器的栈);step7
new MaterialPageRoute<void>(
builder: (BuildContext context){
//在push调用中新建MaterialPageRoute 及其 builder(构造器);
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair){
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
);
//生成 ListTile 行;step7
final List<Widget> divided = ListTile.divideTiles(
//ListTile 的 divideTiles() 方法,使得每个 ListTile 之间有 1 像素的分割线;step7
// divided 变量持有最终的列表项,并通过 toList()方法非常方便的转换成列表显示;step7
context: context,
tiles: tiles,
//新建的tiles在此;step7
).toList(); //直接转换成List;step7
return new Scaffold(
appBar: new AppBar(
title: const Text('Saved Suggestion'),
//名为"Saved Suggestions"的新路由的应用栏;step7
),
body: new ListView(
children: divided,
//body 中的children为divided,divided 包含 ListTiles 行的 ListView 和每行之间的分隔线;step7
),
);
//builder 返回一个 Scaffold;step7
},
),
);//Navigator(导航器)会在应用栏中自动添加一个"返回"按钮;step7
//无需调用Navigator.pop,点击后退按钮就会返回到主页路由;step7
}
//在类中直接添加方法
1⃣️某些 widget 属性需要单个 widget(child),而其它一些属性,如 action,需要一组widgets(children),用方括号 [] 表示;
2⃣️建立一个路由并将其推入到导航管理器栈中会切换页面以显示新路由,新页面的内容会在 MaterialPageRoute 的 builder 属性中构建,builder 是一个匿名函数;
4. Theme change
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
//final wordPair = new WordPair.random();
// add this new line;step2
//remove;step3
return new MaterialApp(
title: 'This is whole title',
//Where does it appear?
theme: new ThemeData.dark(),
//ThemeData's default dark theme;step final
/*ThemeData(
primaryColor: Colors.white,
//通过调用ThemeData类修改app的UI主题;step8
),*/
//official document;step8
/*
home: new Scaffold(
//使用 Scaffold 类实现基础的 Material Design 布局;step4
appBar: new AppBar(
title: const Text('This is appBar title'),
),
body: new Center(//静态Center常量widget树,改为了new widget树对象实例;step2
//child: const Text('This is body\'s child')
//原本是使用const text widget树显示生成text方式;step1
//child: new Text(wordPair.asPascalCase)
//新widget树实例,asPascalCase大驼峰式命名法;step2
child: new RandomWords(),
//利用state类中的build方法来生成随机文本;step3
),
),
*/
home: new RandomWords(),
//用RandomWords()=>build()=>Scaffold()替换原本的一长串Scaffold();step4
);
}
}
1⃣️在MyApp的builder中,增加定义theme的部分;
2⃣️调用ThemeData类中Dart自带的API,或者自定义某些属性,来实现所需要的UI;
6. Training
Task:用flutter写出wechat的大致界面及交互;
If you want to clone this project or update it, you can follow it in Github by click here.
1. MyApp
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return new MaterialApp(
title: 'Let Us Chat',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new PageCount(),
);
}
}
1⃣️ main.dart
初始化MyApp;
2. PageCount
class PageCount extends StatefulWidget{
@override
PageOne createState() => new PageOne();
PageTwo createState() => new PageTwo();
PageThree createState() => new PageThree();
PageFour createState() => new PageFour();
}
1⃣️ 创建 Page Count ,并在其中创建四个页面的状态;
3. PageOne & MainPage
class PageOne extends State<PageCount>{
final List<WordPair> _friends = <WordPair>[];
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
final TextEditingController _controller = new TextEditingController();
int _selectIndex = 0;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('UsChat(2)',textAlign: TextAlign.left),
//导航栏显示文字,设置靠左;
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.search),
iconSize: 16,
onPressed: _onSearch,
alignment: Alignment.centerRight
),
new PopupMenuButton(
icon: new Icon(Icons.add_circle,size: 16),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
new PopupMenuItem(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.chat),
//onPressed: ,
),
new Divider(height: 0,indent: 5.0),
new Text('New Chat')
],
),
),
new PopupMenuItem(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(icon: new Icon(Icons.contacts)),
new Divider(height: 0,indent: 5.0),
new Text('Add Contacts')
],
),
),
new PopupMenuItem(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.scanner),
//onPressed: ,
),
new Divider(height: 0,indent: 5.0),
new Text('Scan')
],
),
),
new PopupMenuItem(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.payment),
//onPressed: ,
),
new Divider(height: 0,indent: 5.0),
new Text('Money')
],
),
),
new PopupMenuItem(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(icon: new Icon(Icons.mail)),
new Divider(height: 0,indent: 5.0),
new Text('Support'),
],
),
)
]
)
],
),
body: _buildFriends(),
bottomNavigationBar: new BottomNavigationBar(
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.chat_bubble_outline,color: Colors.black,),
activeIcon: new Icon(Icons.chat_bubble,color: Colors.green),
title: new Text('Chats'),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.contacts,color: Colors.black),
activeIcon: new Icon(Icons.contacts,color: Colors.green,),
title: new Text('Contacts')
),
new BottomNavigationBarItem(
icon: new Icon(Icons.star_border,color: Colors.black),
activeIcon: new Icon(Icons.star,color: Colors.green),
title: new Text('Discover')
),
new BottomNavigationBarItem(
icon: new Icon(Icons.bookmark_border,color: Colors.black),
activeIcon: new Icon(Icons.bookmark,color: Colors.green),
title: new Text('Me')
)
],
currentIndex: _selectIndex,
onTap: (index) {
setState(() {
_selectIndex = index;
});
},
fixedColor: Colors.green,
),
);
}
Widget _buildFriends(){
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext _context, int i){
if(i.isOdd){
return new Divider();
}
final int index = i ~/ 2;//即 (int)(i/2);step4
if(index >= _friends.length){
new Text('No more to show');
}
return _buildRow(_friends[index]);
}
);
}
Widget _buildRow(WordPair friendsName){
return new ListTile(
leading: Icon(
Icons.picture_in_picture,
),
title: new Text(
friendsName.asPascalCase,
style: _biggerFont,
),
subtitle: new Text('Your friend $friendsName send you a message'),
//onTap: _onTap,
//onLongPress: _onLongPress,
);
}
void _onSearch(){
Navigator.of(context).push(
//表示添加在导航栏部分添加 Navigator.push 调用,实现路由入栈(以后路由入栈均指推入到导航管理器的栈);step7
new MaterialPageRoute<void>(
builder: (BuildContext context){
//在push调用中新建MaterialPageRoute 及其 builder(构造器);
return new Scaffold(
appBar: new AppBar(
title: new Container(
child: new Row(
//不知道为啥不显示;bug
children: <Widget>[
new TextField(
keyboardType: TextInputType.text,
controller: _controller,
cursorColor: Colors.black,
decoration: new InputDecoration(
border: new OutlineInputBorder( //添加边框
gapPadding: 10.0,
borderRadius: BorderRadius.circular(20.0),
),
hintText: 'Search...',
suffixIcon: new IconButton(
icon: new Icon(Icons.send),
onPressed: (){
showDialog(
context: context,
child: new AlertDialog(
content: new Text(_controller.text),
)
);
},
),
contentPadding: const EdgeInsets.all(15.0),
),
//onSubmitted: ,
textInputAction: TextInputAction.done,
)
],
),
)
/*new Text(
'Search...',
textAlign: TextAlign.left
),*/
),
body: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
],
),
),
bottomNavigationBar: new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new TextField(
keyboardType: TextInputType.text,
controller: _controller,
cursorColor: Colors.black,
decoration: new InputDecoration(
border: new OutlineInputBorder( //添加边框
gapPadding: 10.0,
borderRadius: BorderRadius.circular(20.0),
),
hintText: 'Search...',
suffixIcon: new IconButton(
icon: new Icon(Icons.send),
onPressed: (){
showDialog(
context: context,
child: new AlertDialog(
content: new Text(_controller.text),
)
);
},
),
contentPadding: const EdgeInsets.all(15.0),
),
//onSubmitted: ,
textInputAction: TextInputAction.done,
),
new Divider(height: 24.0,color: Colors.white)
],
),
);
},
),
);
}
}
1⃣️ 以上代码仍在完善中,更新日期2019.3.12;
2⃣️ Page One 主要需要实现 Wechat 的 appBar、Body、BottomNavigationBar、以及各个联系人点击进去跳转的 chatting Page;