GridLayout 中如何为 ListModel 移动和挤位添加动画?
可以实现,不过 GridLayout + Repeater 没有直接响应模型移动的过渡接口。如果网格使用统一尺寸的单元格,最直接的做法是改用 GridView:用 move 为被移动的条目添加动画,用 moveDisplaced 处理因此让位的其他条目。如果必须保留 GridLayout,则需要自行实现视觉位置过渡。
为什么 Repeater 不会自动提供移动动画?
Repeater 根据模型创建 delegate,GridLayout 负责排列它们。这个组合没有 GridView 的模型操作过渡机制,无法直接给 GridLayout 设置 move 或 moveDisplaced。
布局还会管理直接子项的位置和尺寸。因此,不建议将直接子项上的 Behavior on x、Behavior on y 作为可靠的布局动画方案:它们会与布局的位置管理相互干扰。布局对几何属性的管理原则见 Qt Quick Layouts 官方说明。
使用 GridView 响应 ListModel.move()
两个过渡分别处理不同的条目:
move:处理模型中被明确移动的条目。moveDisplaced:处理源位置和目标位置之间因移动操作而改变位置的其他条目。
只设置其中一个,只能覆盖其中一类移动。具体语义见 GridView 官方文档。
下面的组件使用 Qt Quick 2.15 的导入方式,可以放入现有窗口。点击任意非末尾条目,就会将它移到末尾:
import QtQuick 2.15
import QtQml.Models 2.15
GridView {
id: grid
width: 360
height: 160
cellWidth: 120
cellHeight: 80
clip: true
model: ListModel {
id: items
ListElement { label: "A" }
ListElement { label: "B" }
ListElement { label: "C" }
ListElement { label: "D" }
ListElement { label: "E" }
ListElement { label: "F" }
}
delegate: Rectangle {
width: grid.cellWidth - 8
height: grid.cellHeight - 8
radius: 6
color: "lightsteelblue"
Text {
anchors.centerIn: parent
text: label
}
MouseArea {
anchors.fill: parent
onClicked: {
if (index < items.count - 1)
items.move(index, items.count - 1, 1)
}
}
}
move: Transition {
NumberAnimation {
properties: "x,y"
duration: 250
easing.type: Easing.InOutQuad
}
}
moveDisplaced: Transition {
NumberAnimation {
properties: "x,y"
duration: 250
easing.type: Easing.InOutQuad
}
}
}
示例中不需要 Repeater,delegate 由 GridView 创建和管理。ListModel.move(from, to, n) 的第三个参数表示移动的条目数量;这里的 1 表示只移动一个条目。参数要求见 ListModel.move() 官方文档。
如果希望新增、删除和移动引起的让位都使用同一种动画,可以用 displaced 替换 moveDisplaced;被明确移动的条目仍由 move 处理。
必须保留 GridLayout 时怎么做?
如果依赖 Layout.rowSpan、Layout.columnSpan,或需要按内容调整行列尺寸,GridView 的统一单元格可能无法满足需求。此时可以让外层 Item 接受布局管理,将实际显示内容放在内部,再通过内部内容的 Translate 实现动画。
一种实现思路是:
- 修改模型前,记录各条目在共同祖先坐标系中的显示位置。
- 执行
ListModel.move(),等待布局完成重新排列。 - 将旧位置与新位置换算到同一坐标系,计算位移差。
- 给内部显示内容设置对应的
Translate偏移,使其暂时停留在旧位置,再将偏移动画到0。
外层占位项会立即到达布局指定的位置,内部内容则平滑移动过去。应使用稳定的条目标识关联移动前后的坐标,因为 index 会随重排改变。连续触发移动时,还需要处理中途动画和新的位置记录。
具体实现取决于 delegate 结构和布局更新时机。原问题没有提供相关代码,因此无法给出适配现有结构的完整实现。
动画没有触发时检查什么?
确认操作确实调用了 ListModel.move()。交换两个条目的角色数据只会更新内容,不等于移动模型条目;清空后重新填充模型,也不能等同于一次移动操作。
首次填充视图的动画由 populate 控制,不能用它判断 move 是否生效。GridView 的 delegate 根项也应交给视图定位,避免再设置约束其位置的 anchors 或坐标绑定。
备注:内容仅供参考。
