前言
上一節中我們一塊學習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<StatefulWidget> createState() => PageState();
}
class PageState extends State<SyllabusPage> {
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<StatefulWidget> createState() => PageState();
}
class PageState extends State<SyllabusPage> {
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課程代碼