前言
上一节中我们一块学习Flutter生命周期相关的基本知识,了解到了在flutter中生命周期函数存在的意义以及各个不同生命周期函数的回调时机,到目前为止我们已经完成了对Flutter所有入门相关的课程学习,掌握了各种常用组件的使用方法以及使用路由来完成页面切换传递数据,还学习了在flutter中的数据存储,网络请求等一系列的相关课程。本次课程作为基础到进阶到过度篇,咱们来一块利用所学知识做一个课程表View,对Flutter相关知识点加以巩固提高,做到活学活用。
1.课程目标
- 分析课表view组成部分,拆解绘制流程
- 课表view绘制,数据准备
- 自行利用所学Widget组合课表view
2.效果图
我们先来看下已经绘制好的课程表View效果图,然后对效果图上的具体实现流程做拆解分析,一步步来完成Flutter课程表view的实现。
从上面的效果图我们可以分析得出,该课表view可分解成下面几个部分,我用不同的颜色块标记出
整体可分为三个大块
- 1 顶部蓝色框框圈住的日期星期区域
- 2 左侧灰色框框圈住的课程节次索引区域
- 3 中间绿色框框圈起来的课程信息区域
下面我们来追一看不同区域的具体实现代码
3. View拆分
3.1 顶部日期星期View
顶部日期View可以拆解为GridView+Column组成,之所以选择GridView是因为我们要做到Column里的数据每一个item都均分显示,GridView设置单行显示,Colum设置上面view是星期,下面view是日期,利用小算法计算出当前日期,然后给当前日期设置不同的样式,来提示用户。
分析了实现思路,具体代码我就不详细讲解了贴上顶部日期星期的具体实现代码供读者参考
星期日期View代码:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/pages/custom_widget/widget/SpaceWidget.dart';
/**
* desc:
* author: xiedong
* date: 4/25/21
**/
class SyllabusPage extends StatefulWidget {
@override
State createState() => PageState();
}
class PageState extends State {
var weekList = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
var dateList = [];
var currentWeekIndex = 0;
@override
void initState() {
super.initState();
var monday = 1;
var mondayTime = DateTime.now();
//获取本周星期一是几号
while (mondayTime.weekday != monday) {
mondayTime = mondayTime.subtract(new Duration(days: 1));
}
mondayTime.year; //2020 年
mondayTime.month; //6(这里和js中的月份有区别,js中是从0开始,dart则从1开始,我们无需再进行加一处理) 月
mondayTime.day; //6 日
// nowTime.hour ;//6 时
// nowTime.minute ;//6 分
// nowTime.second ;//6 秒
for (int i = 0; i < 7; i++) {
dateList.add(
mondayTime.month.toString() + "/" + (mondayTime.day + i).toString());
if ((mondayTime.day + i) == DateTime.now().day) {
setState(() {
currentWeekIndex = i + 1;
});
}
}
// print('Recent monday '+DateTime.now().day.toString());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("我的课表"),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 8,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 8, childAspectRatio: 1 / 1),
itemBuilder: (BuildContext context, int index) {
return Container(
color: index == this.currentWeekIndex
? Color(0xf7f7f7)
: Colors.white,
child: Center(
child: index == 0
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("星期",
style: TextStyle(
fontSize: 14, color: Colors.black87)),
SpaceWidget(height: 5),
Text("日期", style: TextStyle(fontSize: 12)),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(weekList[index - 1],
style: TextStyle(
fontSize: 14,
color: index == currentWeekIndex
? Colors.lightBlue
: Colors.black87)),
SpaceWidget(height: 5),
Text(dateList[index - 1],
style: TextStyle(
fontSize: 12,
color: index == currentWeekIndex
? Colors.lightBlue
: Colors.black87)),
],
),
),
);
}),
),
],
),
);
}
}
运行代码后,效果如下图所示:
3.2 中间课表view
中间课表view跟左侧课程节次指引是在一个大View里处理的,考虑到有些手机可能一屏显示不完整个课程表视图,这里我实现的逻辑是
- 1 先把上图标号为2跟3的区域包裹在一个
SingleChildScrollView
里让整个View支持上下滑动,- 2.然后在
SingleChildScrollView
里用Row
包裹2跟3区域,2是一个GridView
,整体布局1列10行,跟课表view保持高度一样,- 3 .区域3又分为两部分,一个是
背景格子
区域,另外一个带背景颜色的课表区域
,整个3区域我还是利用GridView
实现,- 4 在这里,我默认让每个
课程View即图中标号为4
的区域占两个课程格子的大小,这样一周7天,每天有5大节课,所以GridView
需要设置为5行7列,供35个item,然后让区域3跟左侧2区域高度一致,- 5 区域3
GridView
中的每一个Item采用Stack布局,底下的一层view用Column
包括两个高度一样的Container
,设置好边框,让他呈现格子的样式,顶上的一层试图用来显示课程信息,背景颜色利用提前设置好的颜色数组值,每次随机取不同的值设置不同的颜色,再利用Center
组件显示课程具体信息。- 6 左侧区域2 课程指引view设置相同的背景即可,不需要特殊处理
核心代码如下:
Expanded(
child: SingleChildScrollView(
child: Row(
children: [
Expanded(
flex: 1,
child: GridView.builder(
shrinkWrap: true,
// physics:ClampingScrollPhysics(),
itemCount: 10,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1, childAspectRatio: 1 / 2),
itemBuilder: (BuildContext context, int index) {
return Container(
// width: 25,
// height:s 80,
child: Center(
child: Text(
(index + 1).toInt().toString(),
style: TextStyle(fontSize: 15),
),
),
decoration: BoxDecoration(
color: Color(0xff5ff5),
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 0.5),
right: BorderSide(
color: Colors.black12, width: 0.5),
),
));
}),
),
Expanded(
flex: 7,
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 35,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7, childAspectRatio: 1 / 4),
itemBuilder: (BuildContext context, int index) {
return Container(
child: Stack(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.5),
right: BorderSide(
color: Colors.black12,
width: 0.5),
),
)),
),
Flexible(
flex: 1,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.5),
right: BorderSide(
color: Colors.black12,
width: 0.5),
),
)),
),
],
),
if (index % 5 == 0 || index % 5 == 1)
Container(
margin: EdgeInsets.all(0.5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: colorList[index % 7],
),
child: Center(
child: Text(
infoList[index % 2],
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 11,
letterSpacing: 1),
),
),
)
],
),
);
}),
)
],
),
),
),
运行代码后,中间课程信息View的效果如图
完整代码如下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/pages/custom_widget/widget/SpaceWidget.dart';
/**
* desc:
* author: xiedong
* date: 4/25/21
**/
class SyllabusPage extends StatefulWidget {
@override
State createState() => PageState();
}
class PageState extends State {
var colorList = [
Colors.red,
Colors.lightBlueAccent,
Colors.grey,
Colors.cyan,
Colors.amber,
Colors.deepPurpleAccent,
Colors.purpleAccent
];
var infoList = ["高等数学-周某某教授@综合楼201", "大学英语-王某某讲师@行政楼501"];
var weekList = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
var dateList = [];
var currentWeekIndex = 0;
@override
void initState() {
super.initState();
var monday = 1;
var mondayTime = DateTime.now();
//获取本周星期一是几号
while (mondayTime.weekday != monday) {
mondayTime = mondayTime.subtract(new Duration(days: 1));
}
mondayTime.year; //2020 年
mondayTime.month; //6(这里和js中的月份有区别,js中是从0开始,dart则从1开始,我们无需再进行加一处理) 月
mondayTime.day; //6 日
// nowTime.hour ;//6 时
// nowTime.minute ;//6 分
// nowTime.second ;//6 秒
for (int i = 0; i < 7; i++) {
dateList.add(
mondayTime.month.toString() + "/" + (mondayTime.day + i).toString());
if ((mondayTime.day + i) == DateTime.now().day) {
setState(() {
currentWeekIndex = i + 1;
});
}
}
// print('Recent monday '+DateTime.now().day.toString());
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 8,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 8, childAspectRatio: 1 / 1),
itemBuilder: (BuildContext context, int index) {
return Container(
color: index == this.currentWeekIndex
? Color(0xf7f7f7)
: Colors.white,
child: Center(
child: index == 0
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("星期",
style: TextStyle(
fontSize: 14, color: Colors.black87)),
SpaceWidget(height: 5),
Text("日期", style: TextStyle(fontSize: 12)),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(weekList[index - 1],
style: TextStyle(
fontSize: 14,
color: index == currentWeekIndex
? Colors.lightBlue
: Colors.black87)),
SpaceWidget(height: 5),
Text(dateList[index - 1],
style: TextStyle(
fontSize: 12,
color: index == currentWeekIndex
? Colors.lightBlue
: Colors.black87)),
],
),
),
);
}),
),
Expanded(
child: SingleChildScrollView(
child: Row(
children: [
Expanded(
flex: 1,
child: GridView.builder(
shrinkWrap: true,
// physics:ClampingScrollPhysics(),
itemCount: 10,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1, childAspectRatio: 1 / 2),
itemBuilder: (BuildContext context, int index) {
return Container(
// width: 25,
// height:s 80,
child: Center(
child: Text(
(index + 1).toInt().toString(),
style: TextStyle(fontSize: 15),
),
),
decoration: BoxDecoration(
color: Color(0xff5ff5),
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 0.5),
right: BorderSide(
color: Colors.black12, width: 0.5),
),
));
}),
),
Expanded(
flex: 7,
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 35,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7, childAspectRatio: 1 / 4),
itemBuilder: (BuildContext context, int index) {
return Container(
child: Stack(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.5),
right: BorderSide(
color: Colors.black12,
width: 0.5),
),
)),
),
Flexible(
flex: 1,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
// border: Border.all(color: Colors.black12, width: 0.5),
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.5),
right: BorderSide(
color: Colors.black12,
width: 0.5),
),
)),
),
],
),
if (index % 5 == 0 || index % 5 == 1)
Container(
margin: EdgeInsets.all(0.5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: colorList[index % 7],
),
child: Center(
child: Text(
infoList[index % 2],
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 11,
letterSpacing: 1),
),
),
)
],
),
);
}),
)
],
),
),
),
_bottomView
],
),
);
}
@override
String pageTitle() => "我的课表";
Widget _topView = SizedBox(
height: 30,
child: Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 7,
itemBuilder: (BuildContext context, int index) {
return Text("dd");
}),
),
);
Widget _centerView = Expanded(
child: GridView.builder(
itemCount: 63,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
itemBuilder: (BuildContext context, int index) {
return Container(
// width: 25,
// height: 80,
child: Center(
child: Text(
(index + 1).toString(),
style: TextStyle(fontSize: 15),
),
),
decoration: BoxDecoration(
color: Color(0xff5ff5),
border: Border.all(color: Colors.black12, width: 0.5),
));
}),
);
Widget _bottomView = SizedBox(
height: 30,
child: Row(
children: [
//底部view可自行扩充
],
),
);
}
详细代码已同步更新到我的Flutter入门进阶之旅专栏上,感兴趣的读者可以自行查阅更多相关代码:
仓库地址:本节Flutter课程吧View课程代码