QML可滚动日期选择器构建遇应用冻结问题求助
QML可滚动日期选择器导致应用冻结无响应
我之前实现的时间选择器运行正常,但参考网上代码制作的可滚动日期选择器,在打开包含该组件的页面时会直接导致应用冻结无响应。以下是DatePicker.qml的代码:
import QtQuick 2.0 ListView { id: root function set(date) { // new Date(2019, 10 - 1, 4) selectedDate = new Date(date) positionViewAtIndex((selectedDate.getFullYear()) * 12 + selectedDate.getMonth(), ListView.Center) // index from month year } signal clicked(date date); // onClicked: print('onClicked', date.toDateString()) property date selectedDate: new Date() width: 500; height: 500 // default size snapMode: ListView.SnapOneItem orientation: Qt.Horizontal clip: true model: 3000 * 12 // index == months since January of the year 0 delegate: Item { property int year: Math.floor(index / 12) property int month: index % 12 // 0 January property int firstDay: new Date(year, month, 1).getDay() // 0 Sunday to 6 Saturday width: root.width; height: root.height Column { Item { // month year header width: root.width; height: root.height - grid.height Text { // month year anchors.centerIn: parent text: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][month] + ' ' + year font {pixelSize: 0.5 * grid.cellHeight} } } Grid { // 1 month calendar id: grid width: root.width; height: 0.875 * root.height property real cellWidth: width / columns; property real cellHeight: height / rows // width and height of each cell in the grid. columns: 7 // days rows: 7 Repeater { model: grid.columns * grid.rows // 49 cells per month delegate: Rectangle { // index is 0 to 48 property int day: index - 7 // 0 = top left below Sunday (-7 to 41) property int date: day - firstDay + 1 // 1-31 width: grid.cellWidth; height: grid.cellHeight border.width: 0.3 * radius border.color: new Date(year, month, date).toDateString() == selectedDate.toDateString() && text.text && day >= 0? 'black': 'transparent' // selected radius: 0.02 * root.height opacity: !mouseArea.pressed? 1: 0.3 // pressed state Text { id: text anchors.centerIn: parent font.pixelSize: 0.5 * parent.height font.bold: new Date(year, month, date).toDateString() == new Date().toDateString() // today text: { if(day < 0) ['S', 'M', 'T', 'W', 'T', 'F', 'S'][index] // Su-Sa else if(new Date(year, month, date).getMonth() == month) date // 1-31 else '' } } MouseArea { id: mouseArea anchors.fill: parent enabled: text.text && day >= 0 onClicked: { selectedDate = new Date(year, month, date) root.clicked(selectedDate) } } } } } } } // Component.onCompleted: set(new Date()) // today (otherwise Jan 0000) }
在其他页面中的调用代码:
DatePicker { width: 0.98 * root.width; height: 0.8 * root.width // resize Component.onCompleted: set(new Date()) // today onClicked: print('onClicked', Qt.formatDate(date, 'M/d/yyyy')) }
问题原因与修复方案
核心问题:超大量的Model导致组件爆炸
原代码中model: 3000 * 12意味着ListView会尝试创建36000个月份委托,每个委托又包含49个单元格组件,总计超过170万个组件——这直接耗尽了UI线程资源,导致应用冻结。
具体修复步骤:
- 缩小Model范围
不需要从公元0年开始展示,只保留日常使用的年份区间(比如前后50年),同时调整年份计算逻辑:
model: 100 * 12 // 前后50年,共100年 function set(date) { const startYear = 1974; // 起始年份 selectedDate = new Date(date); // 计算当前日期对应的ListView索引 const index = (selectedDate.getFullYear() - startYear) * 12 + selectedDate.getMonth(); positionViewAtIndex(index, ListView.Center); } delegate: Item { // 从起始年份开始计算当前委托的年份 property int year: 1974 + Math.floor(index / 12) property int month: index % 12 // ... 其他属性 }
- 缓存重复计算的日期值
原代码中多次创建new Date(year, month, date),频繁触发JS垃圾回收,进一步拖慢性能。可以将常用判断缓存为属性:
delegate: Item { // ... 其他属性 property int daysInMonth: new Date(year, month + 1, 0).getDate() // 当月总天数 Repeater { delegate: Rectangle { // ... 其他属性 property bool isCurrentMonth: date > 0 && date <= daysInMonth property bool isSelected: isCurrentMonth && new Date(year, month, date).toDateString() === selectedDate.toDateString() property bool isToday: isCurrentMonth && new Date(year, month, date).toDateString() === new Date().toDateString() // 用缓存属性替代重复计算 border.color: isSelected && day >= 0 ? 'black' : 'transparent' Text { font.bold: isToday text: { if (day < 0) { return ['S', 'M', 'T', 'W', 'T', 'F', 'S'][index] } else if (isCurrentMonth) { return date } else { return '' } } } } } }
- 开启ListView缓存优化
添加cacheBuffer属性,让ListView预加载可见区域外的少量委托,提升滚动流畅度:
ListView { // ... 其他属性 cacheBuffer: 200 // 预加载上下200px范围内的委托 }
修改后,应用不会再因组件过载冻结,同时滚动和交互性能也会大幅提升。
内容的提问来源于stack exchange,提问作者utkuyceng
相关产品推荐
相关产品推荐

