You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI动态列表添加动画:自定义折叠项动画实现求助

SwiftUI 可排序嵌套列表折叠项动画优化方案

针对你遇到的问题,核心是利用SwiftUI新的动画API(绑定具体状态值)来替代已弃用的全局animation(),实现更灵活的折叠动画效果,以下是具体改进方向和代码示例:


关键改进思路

  1. 绑定独立展开状态:给每个父项分配单独的isExpanded状态(可存在模型中,或用@State字典存储,避免修改业务模型),作为动画的触发源。
  2. 用withAnimation包裹状态变更:切换折叠状态时,用withAnimation包裹状态修改操作,指定动画类型(如缓入缓出、弹簧动画),确保视图更新带动画。
  3. 自定义子视图过渡:给展开的子视图添加transition,实现除淡入淡出外的滑动、缩放等动画效果。
  4. 动画绑定到具体值:用animation(_:value:)给单独元素(如折叠箭头)绑定到isExpanded,避免无关动画触发。

改进代码示例

假设你的基础代码结构如下,这里调整为带自定义折叠动画的版本:

// 列表项模型
struct ListItem: Identifiable {
    let id = UUID()
    var name: String
    var children: [ListItem] = []
    // 新增折叠状态(若不想修改模型,可改用@State var expandedStates: [UUID: Bool] = [:])
    var isExpanded: Bool = false
}

struct NestedSortableView: View {
    @State private var items: [ListItem] = [
        ListItem(name: "父项1", children: [ListItem(name: "子项1-1"), ListItem(name: "子项1-2")]),
        ListItem(name: "父项2", children: [ListItem(name: "子项2-1")])
    ]
    @State private var editMode: EditMode = .inactive
    
    var body: some View {
        NavigationStack {
            List {
                ForEach($items) { $parentItem in
                    // 自定义折叠触发按钮,替代系统DisclosureGroup的默认label
                    Button(action: {
                        // 包裹状态切换,触发动画
                        withAnimation(.easeInOut(duration: 0.3)) {
                            parentItem.isExpanded.toggle()
                        }
                    }) {
                        HStack {
                            Text(parentItem.name)
                            Spacer()
                            // 箭头单独添加旋转动画
                            Image(systemName: parentItem.isExpanded ? "chevron.down" : "chevron.right")
                                .rotationEffect(.degrees(parentItem.isExpanded ? 0 : -90))
                                .animation(.easeInOut, value: parentItem.isExpanded)
                        }
                    }
                    
                    // 展开的子视图,添加过渡动画
                    if parentItem.isExpanded {
                        ForEach($parentItem.children) { $childItem in
                            Text(childItem.name)
                                .transition(.opacity.combined(with: .slide))
                        }
                        .onDelete { indices in
                            // 删除子项时也添加动画
                            withAnimation {
                                parentItem.children.remove(atOffsets: indices)
                            }
                        }
                    }
                }
                .onDelete { indices in
                    withAnimation {
                        items.remove(atOffsets: indices)
                    }
                }
                .onMove { source, destination in
                    items.move(fromOffsets: source, toOffset: destination)
                }
            }
            .toolbar {
                EditButton()
            }
            .environment(\.editMode, $editMode)
        }
    }
}

额外注意事项

  • 若坚持使用系统DisclosureGroup,可通过绑定isExpanded并添加animation(_:value:)实现自定义动画:
    DisclosureGroup(isExpanded: $parentItem.isExpanded) {
        // 子视图
    } label: {
        Text(parentItem.name)
    }
    .animation(.easeInOut, value: parentItem.isExpanded)
    
  • 若列表编辑模式和折叠动画冲突,可给List添加条件动画:
    List { /* ... */ }
    .animation(editMode == .active ? nil : .easeInOut, value: editMode)
    

内容的提问来源于stack exchange,提问作者Flamed Dogo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:02:47