Material
Flutter集成了material的组件。
Materia(官网需要科学上网)是谷歌的一套设计风格的组件,它为设计师提供了纸墨风格的整体的设计思路。原生安卓应用也遵循这套设计风格。
以下图片是官网的几个示例。
我们可以通过material组件快速生成一个好看的App。
比如我们可以通过materiApp快速生成一个导航栏,而不用考虑导航栏某个图标的具体位置、某个字的大小。
一个简单基本的结构
废话不多说。先走一个基本的程序。
使用MaterialApp组件,创建一个导航栏和一个hello word的文本。之后详解各个部件使用。
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold( // home 属性为App的入口
appBar: AppBar(
title: Text("这是第一个程序"),
),
body: Center(
child: Text("Hello world"),
),
),
);
}
}
如果我们不使用这种骨架,仍然使用material风格的背景颜色和字体样式。尝试手动写一个MyScaffold的骨架。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyScaffold(),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
MyAppBar(),
Expanded(
child: Center(child: Text("Hello world")),
)
],
),
);
}
}
class MyAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 80,
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
decoration: BoxDecoration(color: Colors.blue[500]),
child: Column(
children: <Widget>[
Spacer(),
Expanded(
flex: 2,
child: Row(
children: <Widget>[
Expanded(
child: Text(
"这是第一个程序",
style: Theme.of(context).primaryTextTheme.title,
),
),
],
),
),
],
),
);
}
}
void main() => runApp(MyApp());
于是代码量变得很大。试想一下。如果从头手写整个骨架。代码量将会更大。
Scaffold脚手架
Scaffold脚手架是整个应用的骨架。我们可以将其抽象成还未被填词的旋律。我们拿到它只需要构建内容就完全可以了。当然,发现提供的旋律不是我们想要的也可以将某一部分替换成其他的,甚至整套样式都不喜欢我们也可将默认的样式都替换。
首先我们看一下scaffold的构造函数的参数。
Scaffold({Key key, PreferredSizeWidget appBar, Widget body, Widget floatingActionButton, FloatingActionButtonLocation floatingActionButtonLocation, FloatingActionButtonAnimator floatingActionButtonAnimator, List<Widget> persistentFooterButtons, Widget drawer, Widget endDrawer, Widget bottomNavigationBar, Widget bottomSheet, Color backgroundColor, bool resizeToAvoidBottomPadding, bool resizeToAvoidBottomInset, bool primary = true, DragStartBehavior drawerDragStartBehavior = DragStartBehavior.start, bool extendBody = false, Color drawerScrimColor, double drawerEdgeDragWidth})
属性 | 构造函数或者类型 |
---|---|
body | Widget |
appbar(导航栏) | Widget |
bottomNavigationBar(底部状态栏) | BottomAppBar |
floatingActionButton(悬浮按钮) | FloatingActionButton |
floatingActionButtonLocation(悬浮按钮位置) | FloatingActionButtonLocation |
drawer(侧边栏) | Widget |
我们再MaterialApp的home属性中添加构造函数Scaffold
创建一个简单的脚手架应用:
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar( //导航栏
title: Text("脚手架组件的示例"),
),
body: Center(child:Text("Scaffold")), // 身体部分
bottomNavigationBar: BottomAppBar( // 底部导航栏
child: Container(
height: 70.0,//高度
),
),
floatingActionButton: FloatingActionButton( // 悬浮按钮
onPressed: (){}, // 点击事件(一般是一个函数)
child: Icon(Icons.add), // 图标样式
tooltip: "增加", // 提示信息
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,// 悬浮按钮的位置
drawer: Icon( //侧边栏
Icons.menu //图标样式
),
),
);
}
}
AppBar导航栏
首先介绍Scaffold的参数appBar。
appBar
属性的参数类型为PreferredSizeWidget
。构造函数是AppBar
。
AppBar({Key key, Widget leading, bool automaticallyImplyLeading = true, Widget title, List<Widget> actions, Widget flexibleSpace, PreferredSizeWidget bottom, double elevation, ShapeBorder shape, Color backgroundColor, Brightness brightness, IconThemeData iconTheme, IconThemeData actionsIconTheme, TextTheme textTheme, bool primary = true, bool centerTitle, double titleSpacing = NavigationToolbar.kMiddleSpacing, double toolbarOpacity = 1.0, double bottomOpacity = 1.0})
在material官网上我们可以看到这样的样式。只要在AppBar的属性写就可以在相应的位置补充内容。
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
title: "appBar示例",
home: Layout(),
));
class Layout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Icon(Icons.menu),
title: Text("AppBar"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
tooltip: '搜索',
onPressed: () {},
),
IconButton(
icon: Icon(Icons.add),
tooltip: '添加',
onPressed: () {},
),
],
),
body: Text("appbar示例"),
);
}
}
导航栏部件
我们看到在materai示例中,还有一个区域为bottom。
我们可以在这个区域创建一个顶部的导航栏。
我们可以使用TabBar
和TabBarView
这两个组件
DefaultTabController
创建TabBar与TabBarView的控制器,关联俩组件的桥梁
DefaultTabController
必须给定 length且必须和TabBar 的tabs 的长度和TabBarView的 children的长度相同(别晕~ ~请看接下来的示例。)。
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
final List<Tab> myTabs =<Tab>[
Tab(text: "1",),
Tab(text: "2",)
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '水平选项卡视图组件',
home: DefaultTabController( // 控制器关联两导航栏和内容
length: 2, // 长度
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: myTabs // 使用上面的变量
),
),
body: TabBarView(
children: myTabs.map( // 后面介绍map的功能
(Tab tab){
return Center(
child: Text(tab.text),
);
}
).toList()
),
),
),
);
}
}
补充内容
final List<Tab> myTabs =<Tab>[
Tab(text: "1",),
Tab(text: "2",)
];
myTabs.map( // 将myTabs的每一个 tab传入
(Tab tab){
return Center(child: Text(tab.text)); // 返回一个 Center
}).toList() // 将全部返回情况的转化为一个列表
一个复杂的导航栏:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class ItemView {
const ItemView({this.title, this.icon});
final String title;
final IconData icon;
}
const items = <ItemView>[
ItemView(title: "自驾", icon: Icons.directions_car),
ItemView(title: "自行车", icon: Icons.directions_bike),
ItemView(title: "船", icon: Icons.directions_boat),
ItemView(title: "公交车", icon: Icons.directions_bus),
ItemView(title: "地铁", icon: Icons.directions_railway),
ItemView(title: "跑", icon: Icons.directions_run),
ItemView(title: "火车", icon: Icons.directions_subway),
ItemView(title: "运输", icon: Icons.directions_transit),
ItemView(title: "走", icon: Icons.directions_walk),
];
// --------------------------------
List directionsTabs = items.map((ItemView item) {
return Tab(
text: item.title,
icon: Icon(item.icon),
);
}).toList();
//---------------------------------
// 这里最好写于tabs属性里 每次热重载时都会调用build方法(详情大家查阅以下statelessWidget的热重载模式)。如果在这里只能用大写R热重载。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "tarbar水平列表篇",
home: DefaultTabController(
length: items.length,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(isScrollable: true, tabs: directionsTabs),
),
body: TabBarView(
children: items.map((item) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: SelectedView(item: item));
}).toList()),
),
));
}
}
class SelectedView extends StatelessWidget {
SelectedView({Key key, this.item}) : super(key: key);
final ItemView item;
@override
Widget build(BuildContext context) {
final TextStyle textStyle = Theme.of(context).textTheme.display1; //提取上一级的主题
return Card(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(item.icon, size: 128.0, color: textStyle.color),
Text(
item.title,
style: textStyle,
)
],
),
),
);
}
}
drawer组件
我们先看看material官方怎么说drawer组件
Drawer组件可以添加头部效果。
UserAccountsDrawerHeader
属性名 | 类型 | 说明 |
---|---|---|
decoration | Decoration | 头部区域的decoration设置背景颜色和样式 |
accountName | Widget | 设置当前用户名 |
accountEmail | Widget | 设置当前用户的邮箱 |
margin | EdgeInsetGeometry | 设置四周间隙 |
OnDetailsPressed | VoidCallback | 回调函数。设置当用户名或者有限被点击时。触发的回调函数。 |
currentAccountPicture | Widget | 用来设置当前用户的头像 |
otherAccountPicture | Widget | 用来设置其他账号头像 |
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: Drawer(
child: ListView(
children: <Widget>[
UserAccountsDrawerHeader( // Hearder的位置信息
accountName: Text("痴痴那"),
accountEmail: Text("1161838951@qq.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: AssetImage("images/1.png"),
),
),
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage("images/1.png"),
),
title: Text("个性装扮"),
),
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage("images/1.png"),
),
title: Text("个性装扮"),
),
ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage("images/1.png"),
),
title: Text("个性装扮"),
),
],
),
),
),
);
}
}
Material的路由管理
第一种方式通过
Navigator.push(context,MaterialPageRoute(builder: (context) => NewRoute())
context这里有个问题详情请看这里
MaterialPageRoute
继承自PageRoute
/// A modal route that replaces the entire screen.
替换整个屏幕的模态路由。
而MaterialPageRoute初始化了PageRoute中的一些属性(动画等)
class MaterialPageRoute<T> extends PageRoute<T>
MaterialPageRoute({
@required this.builder,
RouteSettings settings,
this.maintainState = true,
bool fullscreenDialog = false,
})
Navigator
是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator
通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator
提供了一系列方法来管理路由栈。push
和pop
// 第一种使用路由的方法
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//这里的上下文对象为根部
return MaterialApp(home: HomePage());
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//这里的上下文对象是MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("data"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("home page"),
FlatButton(
child: Text("open new route"),
textColor: Colors.blue,
onPressed: () {
//导航到新路由
Navigator.push( //通过Navigator.push方法
context,
MaterialPageRoute(builder: (context) => NewRoute()),
);
},
),
],
)),
);
}
}
class NewRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("This is new route"),
),
);
}
}
第二种方式
设置materialApp的routes
属性 Navigator.pushNamed(context, '/second');
routes: {
'/frist':(BuildContext context)=>FristPage(),
'/second':(BuildContext context)=>SecondPage()
}
一个使用pushName的例子:注意页面参数的传递以及钩子函数
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => MyHomePage(),
'/a': (context) => MyHomePageA(),
'/b': (context) =>
MyHomePageB(text: ModalRoute.of(context).settings.arguments)
},
initialRoute: '/', // 初始化之后进入的页面
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute(builder: (context) {
return MyHomePageC();
}); // MaterialPageRoute是一个控制路由的
},
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Home Page"),
RaisedButton(
//方式一
child: Text("open A page"),
onPressed: () async {
// 异步函数
// Navigator.pushNamed(context, '/a'); //通过注册路由表来跳转页面、
var a = await Navigator.pushNamed(context, '/a',
arguments: "我是传入的数据");
print("$a"); // await 默认返回一个 Future 可以用then提取
// 返回的数据
},
),
RaisedButton(
child: Text("open B page"),
onPressed: () {
Navigator.of(context).pushNamed('/b', arguments: "我是传入B的参数");
},
),
RaisedButton(
child: Text("跳转到不存在的页面"),
onPressed: () {
Navigator.pushNamed(context, '/c');
},
)
],
),
),
);
}
}
class MyHomePageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
var args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context, "这是传回的数据");
},
)),
body: Center(
child: Text(args),
),
);
}
}
class MyHomePageB extends StatelessWidget {
MyHomePageB({Key key, this.text}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(text),
),
);
}
}
class MyHomePageC extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("123"),
),
);
}
}
按钮和提示组件
我们用上面见到的悬浮按钮FloatingActionButton
生成一个SnackBar的提示
FloatingActionButton
SnackBar
SnackBar在脚手架底部显示一个弹窗,脚手架一次最多可以显示一个SnackBar。如果在另一个SnackBar已经可见的情况下调用此功能,则当前的SnackBar将被添加到队列中并在之前的SnackBar关闭后显示。
使用Scaffold.of(context).showSnackBar(snackBar);
const SnackBar({
Key key,
@required this.content, // SnackBar的内容
this.backgroundColor, // 背景颜色
this.elevation, // 控制阴影的大小 double类型
this.shape, // SnackBar的形状
this.behavior, // 定义了SnackBar的位置和行为
this.action, // 控制SnackBar
this.duration = _snackBarDisplayDuration, // SnackBar 的时间
this.animation, // SnackBar 的动画
})
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final snackBar = new SnackBar(
behavior: SnackBarBehavior.floating,
content: new Text('删除信息'),
action: new SnackBarAction(
label: '撤消',
onPressed: () {
// 设置一些新为信息
}),
);
return MaterialApp(
title: "按钮",
home: Scaffold(
appBar: AppBar(
title: Text("按钮"),
),
body: Builder(builder: (BuildContext context) {
return RaisedButton(
child: Text("隐藏"),
onPressed: () {
Scaffold.of(context).hideCurrentSnackBar();
},
);
}),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: Builder(builder: (BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// Scaffold.of(context).removeCurrentSnackBar(); //清除当前SnackBar
var a = Scaffold.of(context).showSnackBar(snackBar);
// SnackBar(
// content: Text("你点击了"),
// ));
// a.close(); //关闭
},
);
}),
));
}
}
扁平按钮
点击时有一个阴影效果
与悬浮按钮类似都是需要一个回调函数
FlatButton(onPressed: (){},child: Text("扁平悬浮按钮"),),
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "扁平悬浮按钮",
home: Scaffold(
appBar: AppBar(
),
body: Center(
child: FlatButton(
onPressed: (){
// 执行一些操作
},
child: Text("扁平悬浮按钮"),
),
),
),
);
}
}
弹出菜单
PopupMenuButton<T>
构造函数
PopupMenuButton({
Key key,
@required this.itemBuilder, // 必须不为空
this.initialValue,
this.onSelected, // 当选中某项时调用此
this.onCanceled,
this.tooltip,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.icon,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
})
弹出菜单widget使用接口与菜单项关联。
用showMenu函数显示弹出菜单,也可以使用PopupMenuButton去创建按键触发弹出菜单。
[PopupMenuEntry] 代表弹出的列表
- [PopupMenuItem], 单个值的弹出菜单项。
- [PopupMenuDivider], 项的分割线
- [CheckedPopupMenuItem], 单个值的项用标记已选
实例:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
enum ConferenceItem {
AddMember,
LockConference,
ModifyLayout,
TurnoffAll,
TurnoffAllOF
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "弹出菜单",
home: Scaffold(
appBar: AppBar(
title: Text("弹出菜单"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {},
child: PopupMenuButton<ConferenceItem>(
child: Text("打开弹出层"),
onSelected: (ConferenceItem res) {
print(res); // res的值为选中项的value
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<ConferenceItem>>[
PopupMenuDivider(),
PopupMenuItem(
value: ConferenceItem.AddMember,
child: Text("a"),
),
PopupMenuDivider(),
PopupMenuItem(
value: ConferenceItem.LockConference,
child: Text("b"),
),
PopupMenuDivider(),
PopupMenuItem(
value: ConferenceItem.ModifyLayout,
child: Text("c"),
),
PopupMenuDivider(),
PopupMenuItem(
value: ConferenceItem.TurnoffAll,
child: Text("d"),
),
PopupMenuDivider(),
CheckedPopupMenuItem( //
value: ConferenceItem.TurnoffAllOF,
checked: true,
child: Text("e"),
),
],
),
),
],
),
),
),
);
}
}
简单对话框
通常SimpleDialog
(对话框)搭配SimpleDialogOption
(对话框的项)一块使用
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "简单对话框",
home: Scaffold(
appBar: AppBar(title: Text("简单对话框"),),
body: Center(
child: SimpleDialog(
title: Text("对话框"),
children: <Widget>[
SimpleDialogOption(
onPressed: (){},
child: Text("第一行信息"),
),
SimpleDialogOption(
onPressed: (){},
child: Text("第二行信息"),
)
],
),
),
)
);
}
}
提示对话框
AlertDialog
对话框。
const AlertDialog({
Key key,
this.title, // 标头
this.titlePadding, // 标头 内边距
this.titleTextStyle, // 标头的文本样式
this.content, // 内容
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), // 内容的编剧
this.contentTextStyle, // 内容的样式
this.actions, // 按键
this.backgroundColor, // 背景颜色
this.elevation, // z轴坐标
this.semanticLabel,
this.shape, // 边框的形状
})
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "提示对话框",
home: Scaffold(
appBar: AppBar(
title: Text("提示对话框"),
),
body: Center(
child: AlertDialog(
title: Text("提示"),
actions: <Widget>[
FlatButton(
onPressed: () {},
child: Text('确定'),
),
FlatButton(
onPressed: () {},
child: Text('取消'),
),
],
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("是否要删除"),
Text("不可恢复"),
],
),
),
),
)),
);
}
}
文本框组件
TextField提供文本输入的组件
import "package:flutter/material.dart";
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
final TextEditingController controller=TextEditingController() ;
controller.addListener((){
print(controller.text);
});
return MaterialApp(
title: "TextField",
home: Scaffold(
appBar: AppBar(title: Text("data"),),
body: Center(
child: TextField(
controller: controller,
maxLength: 30,
maxLines: 1,
autocorrect: true,
autofocus: true,
textAlign: TextAlign.center,
onChanged: (v){
print("onchange $v");
},
onSubmitted: (v){
print("subumit $v");
},
),
),
),
);
}
}
控制台输出
卡片组件
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
var card = SizedBox(
height: 600,
child: Card(
child: Column(
children: <Widget>[
ListTile(
title: Text(
"清华大学",
style: TextStyle(fontWeight: FontWeight.w300),
),
subtitle: Text("清华大学"),
leading: Icon(Icons.school),
),
Divider(),
ListTile(
title: Text(
"北京大学",
style: TextStyle(fontWeight: FontWeight.w300),
),
subtitle: Text("北京大学"),
leading: Icon(Icons.school),
),
Divider(),
ListTile(
title: Text(
"浙江大学",
style: TextStyle(fontWeight: FontWeight.w300),
),
subtitle: Text("浙江大学"),
leading: Icon(Icons.school),
),
],
),
),
);
return MaterialApp(
title: "card",
home: Scaffold(
appBar: AppBar(
title: Text("card"),
),
body: Center(child: card)),
);
}
}
参考
-
Flutter API文档
-
《Flutter技术入门与实战》