一、介绍
1、dart语言
Dart是谷歌开发的计算机编程语言,后来被Ecma (ECMA-408)认定为标准。它被用于web、服务器、移动应用和物联网等领域的开发。它是宽松开源许可证(修改的BSD证书)下的开源软件。
Dart是面向对象的、类定义的、单继承的语言。它的语法类似C语言,可以转译为JavaScript,支持接口(interfaces)、混入(mixins)、抽象类(abstract classes)、具体化泛型(reified generics)、可选类型(optional typing)和sound type system。
2、flutter框架
Flutter 由 Google 的工程师团队打造,用于创建高性能、跨平台的移动应用。Flutter 针对当下以及未来的移动设备进行优化,专注于 Android and iOS 低延迟的输入和高帧率。
Flutter 可以给开发者提供简单、高效的方式来构建和部署跨平台、高性能移动应用;给用户提供漂亮、快速、jitter-free 的 app 体验。
二、新建Flutter项目
1、Flutter目录结构
文件夹 作用
android android平台相关代码
ios ios平台相关代码
lib flutter相关代码,我们主要编写的代码就在这个文件夹
test 用于存放测试代码
pubspec.yaml 配置文件,一般存放一些第三方库的依赖
2、Flutter入口文件和入口方法
void main(){
runApp(MyApp());
}
//也可以简写
void main() => runApp(MyApp());
//其中的main方法是dart的入口方法。runApp方法是flutter的入口方法。MyApp是自定义的一个组件
3、使用MaterialApp 和 Scaffold两个组件装饰App
1)、MaterialApp
MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。一般作为顶层widget使用。
2)、Scaffold
Scaffold是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。
一个flutter app的基本结构
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('Flutter Demo'),
),
body: HomeContent(),
),
theme: ThemeData(// 主题颜色
primarySwatch: Colors.green
),
);
}
}
三、Flutter组件
1、常用组件
1)、Center
基于父容器居中显示
2)、Text
文本组件
参数:
const Text(
this.data, {
Key key,
this.style,// 字体样式设置
this.strutStyle,
this.textAlign,// 文本对齐方式
this.textDirection,// 文本方向
this.locale,
this.softWrap,
this.overflow,// 文字超出屏幕之后的处理方式
this.textScaleFactor,// 字体显示的倍率
this.maxLines,// 字体显示的最大行数
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
})
3)、Container
容器组件
4)、Image
图片组件
加载网络图片:
Image.network("src")
实现圆形图片(ClipOval组件)
ClipOval(
child:Image.network("src"),
width:150.0,
height:150.0
)
加载本地图片:
2、ListView
1)、循环实现动态列表
//首页
class HomeContent extends StatelessWidget{
//自定义方法 私有方法
List<Widget> _getData(){
var list = listData.map((value){// map循环
return ListTile(
leading: Image.network(value["imageUrl"]),
title:Text(value["title"]),
subtitle: Text(value["author"]),
);
});
return list.toList();
}
@override
Widget build(BuildContext context) {
return ListView(
children: this._getData(),
);
}
}
2)、ListView.builder实现动态列表
//首页
class HomeContent extends StatelessWidget{
Widget _getListData(context,index){
return ListTile(
title: Text(listData[index]['title']),
subtitle: Text(listData[index]['author']),
trailing: Image.network(listData[index]['imageUrl']),
);
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: listData.length,
itemBuilder: this._getListData,// 代表赋值
);
}
}
3、GridView
GridView.count加载动态数据
GridView.builder加载动态数据
//首页
class HomeContent extends StatelessWidget{
Widget _getListData(context,index){
return Container(
child: Column(// 垂直布局
children: <Widget>[
Image.network(listData[index]['imageUrl']),
SizedBox(// 通过sizedbox来划分图片和文本
height: 20,
),
Text(
listData[index]['title'],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20
),
)
],
),
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(233, 233, 233, 0.9),
width: 1,
),
),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(// 配置网格属性
crossAxisSpacing: 10,// 水平 子Widget 距离
mainAxisSpacing: 10,// 垂直 子Widget 距离
crossAxisCount: 2,// 控制一行widget的数量
childAspectRatio: 0.7,// 宽度和高度的比例
),
itemCount: listData.length,
itemBuilder: this._getListData,
);
}
}
4、页面布局
1)、Padding
使用Padding组件处理容器与子元素直接的间距
2)、Row
行布局组件
3)、Column
列布局组件
4)、Expanded
Expanded可以用在Row和Column组件中
属性 说明
flex 元素占整个父Row/Column的比例
child 子元素
5)、Stack
Stack层叠组件(Stack与Align 、 Positioned 实现定位布局)
Align 示例:
//首页
class HomeContent extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400,
width: 200,
color: Colors.red,
child: Stack(// 使用stack 和 align 来控制子元素位置
children: <Widget>[
Align(
alignment: Alignment.center,
child: Icon(Icons.home,size: 20,color: Colors.white,),
),
Align(
alignment: Alignment.bottomCenter,
child: Icon(Icons.search,size: 60,color: Colors.white,),
),
Align(
alignment: Alignment.centerRight,
child: Icon(Icons.gavel,size: 40,color: Colors.white,),
),
],
),
),
);
}
}
Positioned示例:
//首页
class HomeContent extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400,
width: 200,
color: Colors.red,
child: Stack(// 使用stack 和 align 来控制子元素位置
children: <Widget>[
Positioned(
left: 10,
bottom: 0,
child: Icon(Icons.home,size: 20,color: Colors.white,),
),
Positioned(
left: 10,
bottom: 50,
child: Icon(Icons.search,size: 60,color: Colors.white,),
),
Positioned(
right: 10,
bottom: 0,
child: Icon(Icons.gavel,size: 40,color: Colors.white,),
),
],
),
),
);
}
}
5、AspectRatio
AspectRatio的作用是根据设置调整子元素child的宽高比。
AspectRatio首先会在布局限制条件允许的范围内尽可能的扩展,widget的高度是由宽度和比率决定的,类似与BoxFit中的contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio最终将会去优先适应布局限制条件,而忽略所设置的比率。
属性 说明
aspectRatio 宽高比
6、Card
卡片组件
//首页 listData是外部引入的数据
class HomeContent extends StatelessWidget{
List<Widget> _getData(){
var list = listData.map((value){
return Card(
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
AspectRatio(
aspectRatio: 1.0/1.0,
child: Image.network(value['imageUrl']),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(value['imageUrl']),
),
title: Text(value['title'],style: TextStyle(fontSize: 16),),
subtitle: Text(value['author']),
)
],
),
);
});
return list.toList();
}
@override
Widget build(BuildContext context) {
return ListView(
children: this._getData(),
);
}
}
7、Wrap
Wrap可以实现流布局,单行的Wrap跟Row表现几乎一致。但Row和Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。(类似与前端web的flex布局)
四、组件状态
在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget。
StatelessWidget是无状态组件,状态不可变的widget
StatefulWidget是有状态组件,持有的状态可能在widget生命周期改变。通俗的将:如果我们想改变页面中的数据的话这个时候就需要用到StatefulWidget
1、无状态组件
示例:
//无状态组件 无法改变页面中的数据
class HomeContent extends StatelessWidget{
int countNum = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(height: 200,),
Text('${this.countNum}'),
RaisedButton(
child: Text('button'),
onPressed: (){
this.countNum ++;
print(this.countNum);
},
),
],
);
}
}
2、有状态组件
示例:
//有状态组件 能够改变页面中的数据
class HomeContent extends StatefulWidget{
HomeContent({Key key}) : super(key : key);
_HomeContentState createState() => _HomeContentState();
}
class _HomeContentState extends State<HomeContent>{
int countNum = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(height: 20,),
Chip(
label: Text('${this.countNum}'),
),
RaisedButton(
child: Text('button'),
onPressed: (){
setState(() {// 该方法只在有状态组件中有
this.countNum ++;
});
print(this.countNum);
},
),
],
);
}
}
五、BottomNavigationBar 自定义底部导航条
BottomNavigationBar是底部导航条,可以让我们定义底部Tab切换,BottomNavigationBar是Scaffold组件的参数
使用有状态组件来实现tab切换
HomePage(),CategoryPage(),SettingPage() 是tab选项卡对应的三个页面
//Tabs.dart
import 'package:flutter/material.dart';
import 'Home.dart';
import 'Category.dart';
import 'Setting.dart';
//tabs
class Tabs extends StatefulWidget {
@override
_TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
int _currentIndex = 0;
List _pageList = [
HomePage(),
CategoryPage(),
SettingPage()
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: this._pageList[this._currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._currentIndex,// 配置选中的索引值
onTap: (int index){// tab 切换时触发的方法
setState(() {
this._currentIndex = index;
});
},
iconSize: 36,// icon 的大小
fixedColor: Colors.red,// 选中的颜色
type: BottomNavigationBarType.fixed,// 配置底部tabs可以有多个按钮
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('首页'),
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
title: Text('分类'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('设置'),
),
],
),
);
}
}
六、路由
1、基本路由
//路由跳转 push方式 普通路由跳转
Navigator.of(context).push(
MaterialPageRoute(
builder: (context)=>SearchPage(),// 跳转到SearchPage
)
);
Navigator.of(context).pop(); // 返回上一级页面
2、命名路由
1)、配置文件
// 路由配置文件
import 'package:flutter/material.dart';
import 'package:flutter_app/pages/Detail.dart';
import 'package:flutter_app/tabs/Tabs.dart';
import 'package:flutter_app/pages/Product.dart';
import 'package:flutter_app/pages/ProductInfo.dart';
import 'package:flutter_app/pages/user/Login.dart';
import 'package:flutter_app/pages/user/Register.dart';
final routes={
'/':(context)=>Tabs(),
'/detail':(context,{arguments})=>DetailPage(arguments:arguments),
'/product':(context,{arguments})=>ProductPage(arguments:arguments),
'/product_info':(context,{arguments})=>ProductInfoPage(),
'/login':(context)=>LoginPage(),
'/register':(context)=>RegisterPage(),
};// 自定义命名路由
// 命名路由 固定写法
var onGenerateRoute = (RouteSettings settings){// 命名路由传值
//统一处理
final String name = settings.name;// 获取跳转的路由名称
final Function pageContentBuilder = routes[name];
if(pageContentBuilder != null){
if(settings.arguments != null){
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context,arguments:settings.arguments));
return route;
}else{
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context));
return route;
}
}
};
2)、传递参数
// 命名路由跳转
Navigator.pushNamed(context, '/detail',arguments: {
"id":1
});
//Detail.dart
import 'package:flutter/material.dart';
class DetailPage extends StatelessWidget {
final arguments;
DetailPage({this.arguments});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("详情页面"),
),
body: Column(
children: <Widget>[
Text("这是详情页面"),
Text("命名路由传递的参数: ${this.arguments != null ? arguments['id'] : '0'}"),
],
),
);
}
}
3、替换路由
比如我们从用户中心页面跳转到了registerFirst页面,然后从registerFirst页面通过pushReplaceNamed跳转到了registerSecond页面。这个时候我们点击registerSecond的返回按钮的时候它会直接返回到用户中心
Navigator.of(context).pushReplacementNamed('/registerSecond');
4、返回根路由
1)、替换路由实现
每次进行路由跳转时都使用替换路由,则点击返回时直接返回到根路由
2)、pushAndRemoveUntil实现
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(
builder:(context) => new Tabs(),
(route) => route == null
)
)
七、实现顶部TabBar
1、DefaultTabController
步骤:
1)、在Scaffold组件外嵌套一个DefaultTabController组件
2)、Scaffold组件内的appBar组件添加bottom属性
bottom: TabBar(
tabs: <Widget>[
Tab(text: "热门",),
Tab(text: "推荐",)
],
),
3)、Scaffold组件内添加body属性
body: TabBarView(
children: [
ListView(
children: [
ListTile(title: Text('我是第一个tab'),),
ListTile(title: Text('我是第一个tab'),),
ListTile(title: Text('我是第一个tab'),)
],
),
ListView(
children: [
ListTile(title: Text('我是第二个tab'),),
ListTile(title: Text('我是第二个tab'),),
ListTile(title: Text('我是第二个tab'),)
],
),
],
),
2、TabBarController
步骤:
1)、继承SingleTickerProviderStateMixin
2)、定义TabController
3)、初始化tabController
4)、组件中添加controller
5)、为TabController添加监听事件
import 'package:flutter/material.dart';
class TabBarControllerPage extends StatefulWidget {
@override
_TabBarControllerPageState createState() => _TabBarControllerPageState();
}
class _TabBarControllerPageState extends State<TabBarControllerPage>
with SingleTickerProviderStateMixin{// 第一步 继承SingleTickerProviderStateMixin
TabController _tabController;// 第二步 定义TabController
// 生命周期函数
@override
void initState() {// 第三步 初始化tabController
super.initState();
_tabController = new TabController(length: 2, vsync: this);
_tabController.addListener(() {
print(_tabController.index);
setState(() {// 添加自定义功能
});
});// 第五步 为TabController添加监听事件
}
@override
void dispose() { // 组件销毁
super.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBarControllerPage'),
bottom: TabBar(
controller: this._tabController,// 第四步 添加controller
tabs: [
Tab(text: "热销",),
Tab(text: "推荐",)
],
),
),
body: TabBarView(
controller: this._tabController,
children: [
Text('热销'),
Text('推荐'),
],
),
);
}
}
八、侧边栏
侧边栏的主要通过Scaffold组件中的drawer(左侧边栏)和endDrawer(右侧边栏)实现
DrawerHeader(自定义侧边栏头部)
UserAccountsDrawerHeader(侧边栏头部用户模板)