我有一个在水平滚动视图中从右向左流动的按钮列表。滚动会自动发生(动画方式),并且在到达最后一个按钮时应该重置。
我正在处理的两个问题:
下面是我的代码:
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
}
}
}
}
问题:到达显示所有按钮后,它会以跳跃动画重置,并且流动的动画不是很流畅(滞后)
预期结果:按钮应流畅并在到达最后一个按钮时重复,而没有跳跃动画。
我建议使用重复的线性动画,那么你就不需要
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
}
}
}