|
|
@@ -4,32 +4,26 @@ import QtQuick.Layouts 1.12
|
|
|
Item {
|
|
|
id: control
|
|
|
|
|
|
- // ==== 外部接口====
|
|
|
+ // ==== 外部接口:拆分独立属性(核心修复:触发属性更新)====
|
|
|
property bool backgroundVisible: true
|
|
|
property real radius: 20
|
|
|
property int padding: 15
|
|
|
property bool shadowEnabled: true
|
|
|
|
|
|
- // 选中的时分秒
|
|
|
- property var selectedTime: {
|
|
|
- const now = new Date();
|
|
|
- return {
|
|
|
- hour: now.getHours() || 0,
|
|
|
- minute: now.getMinutes() || 0,
|
|
|
- second: now.getSeconds() || 0
|
|
|
- };
|
|
|
- }
|
|
|
- signal timeChanged(var newTime) // 时间变更信号
|
|
|
+ property int selectedHour: new Date().getHours() || 0
|
|
|
+ property int selectedMinute: new Date().getMinutes() || 0
|
|
|
+ property int selectedSecond: new Date().getSeconds() || 0
|
|
|
+ signal timeChanged(string hour, string minute, string second) // 调整信号参数
|
|
|
|
|
|
// 滑动滚轮配置
|
|
|
property int wheelItemHeight: 40 // 每个数值项高度
|
|
|
- property int wheelVisibleCount: 5 // 滚轮可见项数(奇数,确保中间项居中)
|
|
|
+ property int wheelVisibleCount: 5 // 滚轮可见项数(奇数)
|
|
|
property real wheelWidth: 80 // 单个滚轮宽度
|
|
|
|
|
|
property real wheelVisibleHeight: wheelItemHeight * wheelVisibleCount
|
|
|
property real centerOffset: wheelVisibleHeight / 2
|
|
|
implicitWidth: wheelWidth * 3 + padding * 2 + 20
|
|
|
- implicitHeight: wheelVisibleHeight + padding * 2 + 20 // 预留标签高度
|
|
|
+ implicitHeight: wheelVisibleHeight + padding * 2 + 20
|
|
|
|
|
|
function padZero(num) {
|
|
|
const n = Number(num) || 0;
|
|
|
@@ -38,33 +32,44 @@ Item {
|
|
|
|
|
|
function calculateContentY(selectedIndex, totalCount) {
|
|
|
let contentY = (selectedIndex * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
|
|
|
- // 最大值居中的临界值
|
|
|
- const maxContentY = ((totalCount - 1) * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
|
|
|
- // 边界校验:确保contentY在合法范围
|
|
|
+ const maxContentY = ((totalCount - 1) * wheelItemHeight) - (control.centerOffset - wheelItemHeight/2);
|
|
|
contentY = Math.max(0, Math.min(contentY, maxContentY));
|
|
|
return contentY;
|
|
|
}
|
|
|
|
|
|
- function snapToItem(flickable, totalCount) {
|
|
|
- const currentIndex = Math.round((flickable.contentY + control.centerOffset) / wheelItemHeight);
|
|
|
- let targetIndex = (currentIndex % totalCount + totalCount) % totalCount;
|
|
|
- // 计算精准的居中位置
|
|
|
- const targetContentY = calculateContentY(targetIndex, totalCount);
|
|
|
+ // 滑动/拖拽结束后
|
|
|
+ function snapToItem(flickable, totalCount, targetProp) {
|
|
|
+ const rawIndex = (flickable.contentY + control.centerOffset - wheelItemHeight/2) / wheelItemHeight;
|
|
|
+ let targetIndex = Math.round(rawIndex);
|
|
|
+ targetIndex = Math.max(0, Math.min(targetIndex, totalCount - 1));
|
|
|
|
|
|
- flickable.contentY = Qt.binding(function() {
|
|
|
- return targetContentY;
|
|
|
- });
|
|
|
- flickable.flickDeceleration = 2000; // 优化滑动手感
|
|
|
-
|
|
|
- if (flickable === hourWheel) {
|
|
|
- control.selectedTime.hour = targetIndex;
|
|
|
- } else if (flickable === minuteWheel) {
|
|
|
- control.selectedTime.minute = targetIndex;
|
|
|
- } else if (flickable === secondWheel) {
|
|
|
- control.selectedTime.second = targetIndex;
|
|
|
+ const targetContentY = calculateContentY(targetIndex, totalCount);
|
|
|
+ flickable.contentY = targetContentY;
|
|
|
+ flickable.flickDeceleration = 2000;
|
|
|
+
|
|
|
+ if (targetProp === "hour") {
|
|
|
+ control.selectedHour = targetIndex;
|
|
|
+ } else if (targetProp === "minute") {
|
|
|
+ control.selectedMinute = targetIndex;
|
|
|
+ } else if (targetProp === "second") {
|
|
|
+ control.selectedSecond = targetIndex;
|
|
|
}
|
|
|
// 触发时间变更信号
|
|
|
- control.timeChanged(control.selectedTime);
|
|
|
+ control.timeChanged(padZero(control.selectedHour), padZero(control.selectedMinute), padZero(control.selectedSecond));
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateRealTimeValue(flickable, totalCount, targetProp) {
|
|
|
+ const rawIndex = (flickable.contentY + control.centerOffset - wheelItemHeight/2) / wheelItemHeight;
|
|
|
+ let targetIndex = Math.round(rawIndex);
|
|
|
+ targetIndex = Math.max(0, Math.min(targetIndex, totalCount - 1));
|
|
|
+
|
|
|
+ if (targetProp === "hour") {
|
|
|
+ control.selectedHour = targetIndex;
|
|
|
+ } else if (targetProp === "minute") {
|
|
|
+ control.selectedMinute = targetIndex;
|
|
|
+ } else if (targetProp === "second") {
|
|
|
+ control.selectedSecond = targetIndex;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ==== 视觉背景====
|
|
|
@@ -77,20 +82,7 @@ Item {
|
|
|
layer.enabled: control.shadowEnabled && control.backgroundVisible
|
|
|
}
|
|
|
|
|
|
- // ==== 中间选中线 ====
|
|
|
- Rectangle {
|
|
|
- id: centerLine
|
|
|
- anchors.verticalCenter: parent.verticalCenter
|
|
|
- x: padding + 10
|
|
|
- width: wheelWidth * 3 + 20
|
|
|
- height: 2
|
|
|
- color: "transparent"
|
|
|
- border.width: 1
|
|
|
- border.color: "#5a8fc4"
|
|
|
- z: 10
|
|
|
- }
|
|
|
-
|
|
|
- // ==== 主体布局:时/分/秒滚轮并排 ====
|
|
|
+ // ==== 主体布局:时/分/秒滚轮 ====
|
|
|
RowLayout {
|
|
|
id: wheelsContainer
|
|
|
anchors.centerIn: parent
|
|
|
@@ -98,7 +90,7 @@ Item {
|
|
|
spacing: 10
|
|
|
width: wheelWidth * 3 + 20
|
|
|
|
|
|
- // ==== 小时滚轮(含顶部标签)====
|
|
|
+ // ==== 小时滚轮 ====
|
|
|
ColumnLayout {
|
|
|
spacing: 2
|
|
|
|
|
|
@@ -107,29 +99,25 @@ Item {
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
text: "时"
|
|
|
font.pixelSize: 14
|
|
|
- color: "#000000" // 标签固定黑色
|
|
|
+ color: "#000000"
|
|
|
font.bold: true
|
|
|
}
|
|
|
|
|
|
Flickable {
|
|
|
id: hourWheel
|
|
|
width: wheelWidth
|
|
|
- height: wheelVisibleHeight // 改用计算后的可见高度
|
|
|
+ height: wheelVisibleHeight
|
|
|
clip: true
|
|
|
flickableDirection: Flickable.VerticalFlick
|
|
|
- boundsBehavior: Flickable.StopAtBounds // 防止滚动越界
|
|
|
+
|
|
|
+ boundsBehavior: Flickable.DragOverBounds
|
|
|
contentWidth: wheelWidth
|
|
|
- contentHeight: 24 * wheelItemHeight
|
|
|
- contentY: calculateContentY(control.selectedTime.hour, 24)
|
|
|
-
|
|
|
- // 滑动结束后平滑对齐
|
|
|
- onFlickEnded: snapToItem(hourWheel, 24)
|
|
|
- onContentYChanged: {
|
|
|
- const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
|
|
|
- const safeIndex = (previewIndex % 24 + 24) % 24;
|
|
|
- console.log(padZero(safeIndex), safeIndex)
|
|
|
- console.log(control.selectedTime.hour, control.selectedTime.hour === 11);
|
|
|
- }
|
|
|
+ contentHeight: 24 * wheelItemHeight + wheelVisibleHeight/2
|
|
|
+ contentY: calculateContentY(control.selectedHour, 24)
|
|
|
+
|
|
|
+ onFlickEnded: snapToItem(hourWheel, 24, "hour")
|
|
|
+ onDragEnded: snapToItem(hourWheel, 24, "hour")
|
|
|
+ onContentYChanged: updateRealTimeValue(hourWheel, 24, "hour")
|
|
|
|
|
|
Column {
|
|
|
id: hourColumn
|
|
|
@@ -142,19 +130,20 @@ Item {
|
|
|
height: wheelItemHeight
|
|
|
text: padZero(modelData)
|
|
|
font.pixelSize: 18
|
|
|
- font.bold: modelData === control.selectedTime.hour
|
|
|
- color: modelData === control.selectedTime.hour
|
|
|
- ? "#5a8fc4"
|
|
|
- : "#000000"
|
|
|
+
|
|
|
+ font.bold: modelData === control.selectedHour
|
|
|
+ color: modelData === control.selectedHour ? "#5a8fc4" : "#000000"
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
+ // 调试:打印当前值和选中值
|
|
|
+// Component.onCompleted: console.log("Hour delegate:", modelData)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // ==== 分钟滚轮(含顶部标签)====
|
|
|
+ // ==== 分钟滚轮 ====
|
|
|
ColumnLayout {
|
|
|
spacing: 2
|
|
|
|
|
|
@@ -163,7 +152,7 @@ Item {
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
text: "分"
|
|
|
font.pixelSize: 14
|
|
|
- color: "#000000" // 标签固定黑色
|
|
|
+ color: "#000000"
|
|
|
font.bold: true
|
|
|
}
|
|
|
|
|
|
@@ -173,16 +162,14 @@ Item {
|
|
|
height: wheelVisibleHeight
|
|
|
clip: true
|
|
|
flickableDirection: Flickable.VerticalFlick
|
|
|
- boundsBehavior: Flickable.StopAtBounds
|
|
|
+ boundsBehavior: Flickable.DragOverBounds
|
|
|
contentWidth: wheelWidth
|
|
|
- contentHeight: 60 * wheelItemHeight
|
|
|
- contentY: calculateContentY(control.selectedTime.minute, 60)
|
|
|
+ contentHeight: 60 * wheelItemHeight + wheelVisibleHeight/2
|
|
|
+ contentY: calculateContentY(control.selectedMinute, 60)
|
|
|
|
|
|
- onFlickEnded: snapToItem(minuteWheel, 60)
|
|
|
- onContentYChanged: {
|
|
|
- const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
|
|
|
- const safeIndex = (previewIndex % 60 + 60) % 60;
|
|
|
- }
|
|
|
+ onFlickEnded: snapToItem(minuteWheel, 60, "minute")
|
|
|
+ onDragEnded: snapToItem(minuteWheel, 60, "minute")
|
|
|
+ onContentYChanged: updateRealTimeValue(minuteWheel, 60, "minute")
|
|
|
|
|
|
Column {
|
|
|
id: minuteColumn
|
|
|
@@ -195,10 +182,8 @@ Item {
|
|
|
height: wheelItemHeight
|
|
|
text: padZero(modelData)
|
|
|
font.pixelSize: 18
|
|
|
- font.bold: modelData === control.selectedTime.minute
|
|
|
- color: modelData === control.selectedTime.minute
|
|
|
- ? "#5a8fc4"
|
|
|
- : "#000000"
|
|
|
+ font.bold: modelData === control.selectedMinute
|
|
|
+ color: modelData === control.selectedMinute ? "#5a8fc4" : "#000000"
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
}
|
|
|
@@ -216,7 +201,7 @@ Item {
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
|
text: "秒"
|
|
|
font.pixelSize: 14
|
|
|
- color: "#000000" // 标签固定黑色
|
|
|
+ color: "#000000"
|
|
|
font.bold: true
|
|
|
}
|
|
|
|
|
|
@@ -226,16 +211,14 @@ Item {
|
|
|
height: wheelVisibleHeight
|
|
|
clip: true
|
|
|
flickableDirection: Flickable.VerticalFlick
|
|
|
- boundsBehavior: Flickable.StopAtBounds
|
|
|
+ boundsBehavior: Flickable.DragOverBounds
|
|
|
contentWidth: wheelWidth
|
|
|
- contentHeight: 60 * wheelItemHeight
|
|
|
- contentY: calculateContentY(control.selectedTime.second, 60)
|
|
|
+ contentHeight: 60 * wheelItemHeight + wheelVisibleHeight/2
|
|
|
+ contentY: calculateContentY(control.selectedSecond, 60)
|
|
|
|
|
|
- onFlickEnded: snapToItem(secondWheel, 60)
|
|
|
- onContentYChanged: {
|
|
|
- const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
|
|
|
- const safeIndex = (previewIndex % 60 + 60) % 60;
|
|
|
- }
|
|
|
+ onFlickEnded: snapToItem(secondWheel, 60, "second")
|
|
|
+ onDragEnded: snapToItem(secondWheel, 60, "second")
|
|
|
+ onContentYChanged: updateRealTimeValue(secondWheel, 60, "second")
|
|
|
|
|
|
Column {
|
|
|
id: secondColumn
|
|
|
@@ -248,10 +231,8 @@ Item {
|
|
|
height: wheelItemHeight
|
|
|
text: padZero(modelData)
|
|
|
font.pixelSize: 18
|
|
|
- font.bold: modelData === control.selectedTime.second
|
|
|
- color: modelData === control.selectedTime.second
|
|
|
- ? "#5a8fc4"
|
|
|
- : "#000000"
|
|
|
+ font.bold: modelData === control.selectedSecond
|
|
|
+ color: modelData === control.selectedSecond ? "#5a8fc4" : "#000000"
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
}
|
|
|
@@ -261,21 +242,23 @@ Item {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // ==== 生命周期:初始化居中 ====
|
|
|
+ // ==== 初始化 ====
|
|
|
Component.onCompleted: {
|
|
|
Qt.callLater(() => {
|
|
|
- hourWheel.contentY = calculateContentY(control.selectedTime.hour, 24);
|
|
|
- minuteWheel.contentY = calculateContentY(control.selectedTime.minute, 60);
|
|
|
- secondWheel.contentY = calculateContentY(control.selectedTime.second, 60);
|
|
|
+ hourWheel.contentY = calculateContentY(control.selectedHour, 24);
|
|
|
+ minuteWheel.contentY = calculateContentY(control.selectedMinute, 60);
|
|
|
+ secondWheel.contentY = calculateContentY(control.selectedSecond, 60);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // ==== 监听外部修改selectedTime,同步更新滚轮位置 ====
|
|
|
- onSelectedTimeChanged: {
|
|
|
- Qt.callLater(() => {
|
|
|
- hourWheel.contentY = calculateContentY(Number(control.selectedTime.hour) || 0, 24);
|
|
|
- minuteWheel.contentY = calculateContentY(Number(control.selectedTime.minute) || 0, 60);
|
|
|
- secondWheel.contentY = calculateContentY(Number(control.selectedTime.second) || 0, 60);
|
|
|
- });
|
|
|
- }
|
|
|
+ // ==== 监听外部修改(独立属性)====
|
|
|
+ onSelectedHourChanged: Qt.callLater(() => {
|
|
|
+ hourWheel.contentY = calculateContentY(control.selectedHour, 24);
|
|
|
+ })
|
|
|
+ onSelectedMinuteChanged: Qt.callLater(() => {
|
|
|
+ minuteWheel.contentY = calculateContentY(control.selectedMinute, 60);
|
|
|
+ })
|
|
|
+ onSelectedSecondChanged: Qt.callLater(() => {
|
|
|
+ secondWheel.contentY = calculateContentY(control.selectedSecond, 60);
|
|
|
+ })
|
|
|
}
|