PAYATHON 2026

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 实现动画。

一种实现思路是:

  1. 修改模型前,记录各条目在共同祖先坐标系中的显示位置。
  2. 执行 ListModel.move(),等待布局完成重新排列。
  3. 将旧位置与新位置换算到同一坐标系,计算位移差。
  4. 给内部显示内容设置对应的 Translate 偏移,使其暂时停留在旧位置,再将偏移动画到 0。

外层占位项会立即到达布局指定的位置,内部内容则平滑移动过去。应使用稳定的条目标识关联移动前后的坐标,因为 index 会随重排改变。连续触发移动时,还需要处理中途动画和新的位置记录。

具体实现取决于 delegate 结构和布局更新时机。原问题没有提供相关代码,因此无法给出适配现有结构的完整实现。

动画没有触发时检查什么?

确认操作确实调用了 ListModel.move()。交换两个条目的角色数据只会更新内容,不等于移动模型条目;清空后重新填充模型,也不能等同于一次移动操作。

首次填充视图的动画由 populate 控制,不能用它判断 move 是否生效。GridView 的 delegate 根项也应交给视图定位,避免再设置约束其位置的 anchors 或坐标绑定。

备注:内容仅供参考。