flutter自定义table_calendar
没想到吧,我又开始写flutter了,因为我现在是写海外APP的,uniapp虽然简单快捷,但是对于海外某些sdk的接入难度会直线飙升,原生集成,封装原生方法,封装原生视图之类的,本来是个web前端小菜鸡,不会Android,现在也算是略懂一点点Android了。因为海外很多sdk对flutter的支持度还算可以,又因之前闲暇之余有搞过flutter Hello World 的项目经历,加之二月到六月因为工作需要,将一个Android app转鸿蒙原生开发,所以熟悉flutter也还算快,所以就开始搞flutter开发了。
扯远了,进入正题,因为项目涉及到日历相关的,就在网上找了一下,最后选择了 table_calendar,因为这个出了新的,删除了某些api,又新增了某些api,导致部分自定义功能搜索出来的代码还是之前的版本,所以最后参考官网实现了,做个简单的记录,方便后续查阅(其实官方文档里面都有......)。
1. 基本使用
按照官方文档,写入必要的一些参数,就行了
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime currentTime = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: TableCalendar(
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
),
),
);
}
}
这样就得到了一个很简单的日历组件
接下来我们尝试更改一些基本的样式
1.1 修改头部样式
使用 headerStyle 属性,我们可以很快的更改头部的样式
// 其他代码就省略...
TableCalendar(
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
headerStyle: const HeaderStyle(
// 将头部的padding和margin设置为0
headerPadding: EdgeInsets.zero,
headerMargin: EdgeInsets.zero,
// 设置头部显示的日期居中
titleCentered: true,
// 设置头部显示的日期文本样式
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold
),
// 隐藏formatButton
formatButtonVisible: false,
// 设置左右切换按钮的margin
leftChevronMargin: EdgeInsets.only(left: 40),
rightChevronMargin: EdgeInsets.only(right: 40)
)
),
当然还可以设置一些其他的,基本上见名知意
1.2 修改星期栏的样式
使用 daysOfWeekStyle 和 daysOfWeekHeight 可以很好的更改其样式
// 其他代码就省略...
TableCalendar(
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
// 设置展示星期栏的高度
daysOfWeekHeight: 40,
// 设置展示星期栏的样式
daysOfWeekStyle: const DaysOfWeekStyle(
// 设置星期一到星期五的文本样式
weekdayStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF666666)
),
// 设置星期六和星期天的文本样式
weekendStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFFFF0000)
),
// 设置星期容器的样式
decoration: BoxDecoration(
color: Color(0xFFC0C0C0),
border: Border(
bottom: BorderSide(
width: 1,
color: Color(0xFF000000)
)
)
)
),
),
其他属性可以自行设置体验
1.3 修改日历日期的样式
TableCalendar(
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
// 设置日历横行的高度
rowHeight: 50,
calendarStyle: const CalendarStyle(
// 设置除周末外的日历文本样式
defaultTextStyle: TextStyle(
fontSize: 20,
color: Color(0xFF666666),
fontWeight: FontWeight.w600
),
// 设置周末的文本样式
weekendTextStyle: TextStyle(
fontSize: 20,
color: Color(0xFFFF0000),
fontWeight: FontWeight.bold
),
// 设置当前日期的文本样式
todayTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF)
),
// 设置当前日期的容器样式
todayDecoration: BoxDecoration(
color: Color(0xFFFF657D),
shape: BoxShape.circle,
)
),
),
其他属性可以自行设置体验
通过以上属性就可以更改大部分样式,接下来我们使用其他属性进行更加精细的自定义
2. 精细自定义
2.1 自定义头部容器
如果我要对头部新增一些按钮图标,或者控制某些样式,这样我们就需要使用 calendarBuilders 和 headerStyle 属性来自定义头部
class _MyHomePageState extends State<MyHomePage> {
DateTime currentTime = DateTime.now();
// 用于自定义切换上一月和下一月
late final PageController _pageController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: TableCalendar(
// 监听日历组件创建,设置_pageController
onCalendarCreated: (pageController) {
_pageController = pageController;
},
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
calendarBuilders: CalendarBuilders(
// 所以自定义头部Builder,我们就可以自定义头部小部件了
headerTitleBuilder: (context, dateTimeInfo) {
return Column(
children: [
Stack(
children: [
Positioned(
left: 20,
top: 3,
child: GestureDetector(
onTap: () {
// 处理事件
},
child: Image.asset(
'assets/images/icon_to_record.png',
width: 26,
height: 26,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 自定义切换上一月按钮
GestureDetector(
onTap: () {
// 切换上一月
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
child: SizedBox(
width: 20,
height: 20,
child: Image.asset(
'assets/images/icon_back.png',
width: 6,
height: 11,
),
),
),
const SizedBox(width: 20,),
Text(
'${dateTimeInfo.month}-${dateTimeInfo.year}',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold
),
),
const SizedBox(width: 20,),
// 自定义切换上一月按钮
GestureDetector(
onTap: () {
// 切换上一月
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
child: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..rotateZ(math.pi),
child: SizedBox(
width: 20,
height: 20,
child: Image.asset(
'assets/images/icon_back.png',
width: 6,
height: 11,
),
),
)
),
],
)
],
),
Container(
width: 200,
height: 40,
decoration: BoxDecoration(
color: const Color(0x33000000),
borderRadius: BorderRadius.circular(20)
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'这是二排文本',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600
),
),
const SizedBox(width: 10,),
GestureDetector(
onTap: () {
// 处理点击事件
},
child: const SizedBox(
width: 20,
height: 20,
child: Text('+', style: TextStyle(fontSize: 16),),
),
)
],
),
)
],
);
}
),
headerStyle: const HeaderStyle(
titleCentered: true,
// 隐藏formatButton
formatButtonVisible: false,
// 因为我们要自定义布局,所以隐藏这两个按钮,默认这两个按钮等分剩余的宽度
// 隐藏左边切换上一月按钮
leftChevronVisible: false,
// 隐藏右边切换下一月按钮
rightChevronVisible: false
),
),
),
);
}
}
2.2 自定义选中日期
TableCalendar(
// 可选时间区间的开始时间
firstDay: DateTime.utc(2000, 1, 1),
// 可选时间区间的结束时间
lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
// 当前获取焦点的时间
focusedDay: currentTime,
// 设置当前选中日期
selectedDayPredicate: (day) => isSameDay(currentTime, day),
calendarStyle: const CalendarStyle(
// 设置当前日期的样式
todayDecoration: BoxDecoration(
color: Colors.amber,
shape: BoxShape.circle,
),
// 设置选中的日期容器样式
selectedDecoration: BoxDecoration(
color: Color(0xFFFF657D),
shape: BoxShape.circle,
),
),
// 点击选中日期
onDaySelected: (selectedDate, focusedDay) {
setState(() {
currentTime = selectedDate;
});
},
),
至此,结束,后续还需要自定义还可以参照文档更改即可,就目前这样也可以满足很多场景了。