效果展示
主要特点为:
- 选择器分三个层面:日期选择、月份选择、年份选择,三个层面均具备类似Win11选择器的过渡;
- 所有层面均支持触摸滑动、鼠标滚动、按键翻页,翻页后内部日期同步高亮;
- 当前日期底层高亮,已选日期边框加黑;
- 支持日期选错后的恢复。
经验总结
这个日历选择器前前后后搞了近3天时间,终于将代码优化至自我认为比较友好的层度,具体内容不重要,大家花时间都能做出来,主要是想通过这几天的实践总结一下对Js的更深入理解:
- Js采用了一种属性绑定的形式,初始化绑定是一种深拷贝,具体代码以及运行结果如下:
property var selectDate: lastSelectDate //当前选择的时间
property var lastSelectDate: new Date() //上一次选择时间
Component.onCompleted: {
lastSelectDate.setFullYear(lastSelectDate.getFullYear() + 1)
console.log(selectDate, lastSelectDate)
//此时selectDate不变,lastSelectDate增加1年
}
Component.onCompleted: {
lastSelectDate.setFullYear(lastSelectDate.getFullYear() + 1)
lastSelectDateChanged()
console.log(selectDate, lastSelectDate)
//此时selectDate和lastSelectDate均增加1年
}
Component.onCompleted: {
lastSelectDate= new Date()
console.log(selectDate, lastSelectDate)
//此时selectDate和lastSelectDate均变为最新时间
}
- 从上代码可以理解,当lastSelectDate= new Date()时,后台自动运行lastSelectDateChanged(),此时与lastSelectDate绑定的所有属性均更新,然而运行lastSelectDate.setFullYear(lastSelectDate.getFullYear() + 1)时,lastSelectDate指向的栈区并无变化,后台不运行lastSelectDateChanged(),因此相关属性均不更新,但若手动运行lastSelectDateChanged(),则绑定lastSelectDate的所有属性均再一次更新。此处与C++创建qml属性及其类似。参考QtCreartor自动创建属性时的代码:
void TestItem::setItemNameList(const QStringList& newItemNameList)
{
if (_itemNameList == newItemNameList)
return;
_itemNameList = newItemNameList;
emit itemNameListChanged();
}
主要代码说明
首先创建了3个组件,分别是日期选择窗口、月份选择窗口、年份选择窗口
Component {
id: _day
Rectangle {
color: background
ColumnLayout {
anchors.fill: parent
DayOfWeekRow {
locale: Qt.locale("zh_CN")
Layout.fillWidth: true
delegate: Text {
text: shortName
font.bold: true
color: "black"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
required property string shortName
}
}
ListView {
id: _listview
Layout.fillWidth: true
Layout.fillHeight: true
model: 1200
snapMode: ListView.SnapOneItem
clip: true
spacing: 20
currentIndex: (selectDate.getFullYear(
) - 2000) * 12 + selectDate.getUTCMonth()
highlightMoveDuration: 100
highlightMoveVelocity: -1
highlightRangeMode: ListView.StrictlyEnforceRange
delegate: MonthGrid {
id: month_grid
width: _listview.width
height: _listview.height
month: index % 12
year: 2000 + Math.floor(index / 12)
locale: Qt.locale("zh_CN")
font {
family: "SimHei"
pixelSize: 14
}
delegate: AbstractButton {
id: _controlv
text: model.day
property bool isCurrentDate: _root.selectDate.getFullYear(
) === model.date.getFullYear()
&& _root.selectDate.getMonth(
) === model.date.getMonth()
&& _root.selectDate.getDate(
) === model.date.getDate()
background: Rectangle {
id: _rect
anchors.fill: parent
border.width: 2
border.color: _controlv.down
|| isCurrentDate ? "black" : (_controlv.hovered ? "#888888" : "transparent")
color: model.today ? accent : "transparent"
}
contentItem: Text {
font: _controlv.font
text: _controlv.text
color: _controlv.down
|| isCurrentDate ? "black" : (model.month === month_grid.month ? "black" : "#888888")
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {
_root.selectDate = model.date
selectDateChanged()
}
}
}
onMovementEnded: {
selectDate.setUTCFullYear(
2000 + Math.floor(
_listview.currentIndex / 12))
selectDate.setUTCMonth(_listview.currentIndex % 12)
selectDateChanged()
}
}
}
}
}
Component {
id: _month
GridView {
id: _gridview
model: 1200
clip: true
cellHeight: height / 4
cellWidth: width / 4
highlightRangeMode: GridView.StrictlyEnforceRange
preferredHighlightBegin: height / 4
preferredHighlightEnd: height * 3 / 4
currentIndex: (selectDate.getFullYear(
) - 2000) * 12 + selectDate.getUTCMonth()
delegate: AbstractButton {
id: _controlv
text: (index % 12 + 1) + "月"
height: _gridview.height / 4
width: _gridview.width / 4
property bool isCurrentYear: ((2000 + Math.floor(
index / 12)) === selectDate.getUTCFullYear(
))
property bool isCurrentMonth: ((2000 + Math.floor(
index / 12)) === selectDate.getUTCFullYear(
))
&& (index % 12 === selectDate.getUTCMonth(
))
background: Rectangle {
anchors.fill: parent
border.width: 2
border.color: _controlv.down
|| isCurrentMonth ? "black" : (_controlv.hovered ? "gray" : "transparent")
color: currentYearIndex === index ? accent : "transparent"
}
contentItem: Text {
font: _controlv.font
text: _controlv.text
color: _controlv.down ? "black" : (isCurrentYear ? "black" : "#888888")
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {
selectDate.setUTCFullYear(Math.floor(index / 12) + 2000)
selectDate.setUTCMonth(index % 12)
selectDateChanged()
_stackviewv.pop()
}
}
onMovementEnded: {
selectDate.setUTCFullYear(2000 + Math.floor(
_gridview.currentIndex / 12))
selectDate.setUTCMonth(_gridview.currentIndex % 12)
selectDateChanged()
}
}
}
Component {
id: _year
GridView {
id: _gridview
model: 100
clip: true
cellHeight: height / 4
cellWidth: width / 4
highlightRangeMode: GridView.StrictlyEnforceRange
preferredHighlightBegin: height / 4
preferredHighlightEnd: height * 3 / 4
currentIndex: selectDate.getFullYear() - 2000
delegate: AbstractButton {
id: _controlv
text: (2000 + index) + "年"
height: _gridview.height / 4
width: _gridview.width / 4
property bool isCurrentDecade: ((2000 + index) >= (Math.floor(
selectDate.getUTCFullYear()
/ 10) * 10))
&& ((2000 + index)
<= (Math.floor(selectDate.getUTCFullYear(
) / 10) * 10 + 9))
property bool isCurrentYear: ((2000 + index) === selectDate.getUTCFullYear(
))
background: Rectangle {
anchors.fill: parent
border.width: 2
border.color: _controlv.down
|| isCurrentYear ? "black" : (_controlv.hovered ? "gray" : "transparent")
color: currentDecadeIndex === index ? accent : "transparent"
}
contentItem: Text {
font: _controlv.font
text: _controlv.text
color: _controlv.down ? "black" : (isCurrentDecade ? "black" : "#888888")
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {
selectDate.setUTCFullYear(index + 2000)
selectDateChanged()
_stackviewv.pop()
}
}
onMovementEnded: {
selectDate.setUTCFullYear(2000 + _gridview.currentIndex)
selectDateChanged()
}
}
}
三个窗口通过StackView组件进行切换,每个窗口均需要实现拖拽、滚轮滚动、反面的联动绑定。代码过几天我会发布出来,先记录到这主要是为了总结这几天的收获。