如何重置水平无限滚动视图动画中动态大小按钮列表的偏移量

问题描述 投票:0回答:1

我有一个在水平滚动视图中从右向左流动的按钮列表。滚动会自动发生(动画方式),并且在到达最后一个按钮时应该重置。

我正在处理的两个问题:

  1. 到达最后一个按钮后,它会重置偏移量,但会执行跳跃动画,没有平滑重置
  2. 按钮流程没有流畅的动画(有点滞后)

下面是我的代码:

 let buttonData = [
        ["🕯️ Did Edison invent the lightbulb?", "🚴‍♂️ How do you ride a bike?"],
        ["⛷️ The best ski resorts in the Alps", "🏟️ The greatest Super Bowl moments", "🎬 Best movies of all time"],
        ["🥊 The best boxing style", "🐩 The best dog breed for apartments","🏖️ Top beach destinations"],
    ]
@State private var offsetX: CGFloat = 0 // To move the buttons horizontally
    @State private var widthOfSingleButton: CGFloat = 0 // Holds the width of one button
    
    var body: some View {
            // Measuring one button's width to manage the scroll loop
            GeometryReader { geometry in
                ScrollView(.horizontal, showsIndicators: false) {
                    VStack{
                        ForEach(0..<buttonData.count, id: \.self) { rowIndex in
                            HStack(spacing: 16) {
                                ForEach(buttonData[rowIndex], id: \.self) { item in
                                    Button(action: {
                                        // Button action
                                    }) {
                                        Text(item)
                                            .padding()
                                            .background(Color.gray.opacity(0.1))
                                            .cornerRadius(5)
                                            .foregroundColor(.black)
                                    }
                                    .background(GeometryReader { buttonGeo -> Color in
                                        DispatchQueue.main.async {
                                            if self.widthOfSingleButton == 0 {
                                                self.widthOfSingleButton = buttonGeo.size.width + 16 // button width + spacing
                                            }
                                        }
                                        return Color.clear
                                    })
                                }
                            }
                            .offset(x: offsetX)
                            .onAppear {
                                startScrolling(totalButtonsWidth: geometry.size.width)
                            }
                        }
                    }
                    
                }
            }
            .frame(height: 200) // Constrain the height of the button area
            .frame(maxWidth: UIScreen.main.bounds.width) // Constrain the width of the button area
        }
    
    
    func startScrolling(totalButtonsWidth: CGFloat) {
        // Animation loop to slowly move the buttons horizontally
        let baseSpeed: CGFloat = 50 // Speed of the scroll (points per second)
        
        Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
            withAnimation(.linear(duration: 0.1)) {
                // Continuously move to the left
                offsetX -= baseSpeed * 0.02
                
                // Calculate the total width of all buttons
                let totalWidth = widthOfSingleButton * CGFloat(buttonData.count)
                
                // Loop the scroll when reaching the end
                if offsetX < -totalWidth {
                    offsetX = 0
                }
            }
        }
    }

问题:到达显示所有按钮后,它会以跳跃动画重置,并且流动的动画不是很流畅(滞后)

预期结果:按钮应流畅并在到达最后一个按钮时重复,而没有跳跃动画。

ios swift swiftui
1个回答
0
投票

我建议使用重复的线性动画,那么你就不需要

Timer

唯一需要的输入如下:

  • 屏幕的宽度。最好使用

    GeometryReader
    来测量,而不是读取
    UIScreen.main
    。这是因为
    UIScreen.main
    无法在 iPad 分屏模式下正常工作,并且已被弃用。

  • 所有按钮的宽度。无需使用

    GeometryReader
    来测量每个按钮的宽度,只需在后台使用
    VStack
    来测量
    GeometryReader
    的整个宽度即可。

其他建议:

  • 外部

    ScrollView
    不需要。但是,如果不存在,则需要将
    .fixedSize()
    应用于按钮标签,以阻止文本被截断。

  • 将偏移量应用于

    VStack
    ,而不是应用于
    HStack
    内的每个
    VStack

  • 您的原始动画从屏幕左侧的按钮开始,然后当它们全部移出屏幕时重置。您可能会考虑从屏幕空白开始,然后按钮从右到左一直移动。这样可以有更多时间查看左侧的按钮。恕我直言,重置后看起来也更好。

  • 动画的持续时间可以根据总宽度计算。我发现每 100 点 3 秒效果很好。

  • 修饰符

    foregroundColor
    已弃用,请使用
    .foregroundStyle
    代替。

  • 修饰符

    .cornerRadius
    也已弃用。您可以使用带有
    RoundedRectangle
    的剪辑形状,或者仅在背景中显示
    RoundedRectangle

这是更新的示例,展示了它的工作方式:

struct ContentView: View {
    let buttonData = [
        ["🕯️ Did Edison invent the lightbulb?", "🚴‍♂️ How do you ride a bike?"],
        ["⛷️ The best ski resorts in the Alps", "🏟️ The greatest Super Bowl moments", "🎬 Best movies of all time"],
        ["🥊 The best boxing style", "🐩 The best dog breed for apartments","🏖️ Top beach destinations"],
    ]
    @State private var offsetX: CGFloat = 0 // To move the buttons horizontally

    var body: some View {
        GeometryReader { outer in
            let displayWidth = outer.size.width
            VStack {
                ForEach(0..<buttonData.count, id: \.self) { rowIndex in
                    HStack(spacing: 16) {
                        ForEach(buttonData[rowIndex], id: \.self) { item in
                            Button {
                                // Button action
                            } label: {
                                Text(item)
                                    .padding()
                                    .fixedSize()
                                    .foregroundStyle(.black)
                                    .background {
                                        RoundedRectangle(cornerRadius: 5)
                                            .fill(.gray.opacity(0.1))
                                    }
                            }
                        }
                    }
                }
            }
            .offset(x: offsetX)
            .background {
                GeometryReader { proxy in
                    Color.clear
                        .onAppear {
                            offsetX = displayWidth // Push the buttons off-screen to the right
                            startScrolling(scrolledWidth: proxy.size.width + displayWidth)
                        }
                }
            }
        }
        .frame(height: 200) // Constrain the height of the button area
    }

    private func startScrolling(scrolledWidth: CGFloat) {
        withAnimation(.linear(duration: scrolledWidth * 3 / 100).repeatForever(autoreverses: false)) {
            offsetX -= scrolledWidth
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.