如何实现Swift框架iCarousel中的旋转式走马灯。以下是我想在SwiftUI中实现的内容。
我查看了许多教程和现有的框架,但无法像上面的图片所示那样实现。
以下是一般的方法:所有项目都在ZStack
中相互叠放,然后根据到最前面元素的“距离”更改它们的位置和不透明度。
该演示对项目有固定大小,但可以轻松适应。根据您的需要更改opacity
和scaleEffect
的值。
struct Item: Identifiable {
var id: Int
var title: String
var color: Color
}
class Store: ObservableObject {
@Published var items: [Item]
let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo, .black]
// dummy data
init() {
items = []
for i in 0...7 {
let new = Item(id: i, title: "Item \(i)", color: colors[i])
items.append(new)
}
}
}
struct ContentView: View {
@StateObject var store = Store()
@State private var snappedItem = 0.0
@State private var draggingItem = 0.0
var body: some View {
ZStack {
ForEach(store.items) { item in
// article view
ZStack {
RoundedRectangle(cornerRadius: 18)
.fill(item.color)
Text(item.title)
.padding()
}
.frame(width: 200, height: 200)
.scaleEffect(1.0 - abs(distance(item.id)) * 0.2 )
.opacity(1.0 - abs(distance(item.id)) * 0.3 )
.offset(x: myXOffset(item.id), y: 0)
.zIndex(1.0 - abs(distance(item.id)) * 0.1)
}
}
.gesture(
DragGesture()
.onChanged { value in
draggingItem = snappedItem + value.translation.width / 100
}
.onEnded { value in
withAnimation {
draggingItem = snappedItem + value.predictedEndTranslation.width / 100
draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count))
snappedItem = draggingItem
}
}
)
}
func distance(_ item: Int) -> Double {
return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count))
}
func myXOffset(_ item: Int) -> Double {
let angle = Double.pi * 2 / Double(store.items.count) * distance(item)
return sin(angle) * 200
}
}
.scaleEffect
并调整 myXOffset
中的半径,例如从 200 调整到 300。当然,这个 "carousel" 仍然会将项目保持在一个圆圈中。看起来你想要完全线性的效果... - undefined谢谢@ChrisR,这是实现Carousel体验的好方法。
在@ChrisR的答案中增加了活动索引,这可能对某些人有用。
@ChrisR,一旦您在答案中添加了活动索引,我就可以删除我的帖子了。
import SwiftUI
struct Item: Identifiable {
var id: Int
var title: String
var color: Color
}
class Store: ObservableObject {
@Published var items: [Item]
let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo, .black]
// dummy data
init() {
items = []
for i in 0...7 {
let new = Item(id: i, title: "Item \(i)", color: colors[i])
items.append(new)
}
}
}
struct ContentView: View {
@StateObject var store = Store()
@State private var snappedItem = 0.0
@State private var draggingItem = 0.0
@State var activeIndex: Int = 0
var body: some View {
ZStack {
ForEach(store.items) { item in
// article view
ZStack {
RoundedRectangle(cornerRadius: 18)
.fill(item.color)
Text(item.title)
.padding()
}
.frame(width: 200, height: 200)
.scaleEffect(1.0 - abs(distance(item.id)) * 0.2 )
.opacity(1.0 - abs(distance(item.id)) * 0.3 )
.offset(x: myXOffset(item.id), y: 0)
.zIndex(1.0 - abs(distance(item.id)) * 0.1)
}
}
.gesture(
DragGesture()
.onChanged { value in
draggingItem = snappedItem + value.translation.width / 100
}
.onEnded { value in
withAnimation {
draggingItem = snappedItem + value.predictedEndTranslation.width / 100
draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count))
snappedItem = draggingItem
//Get the active Item index
self.activeIndex = store.items.count + Int(draggingItem)
if self.activeIndex > store.items.count || Int(draggingItem) >= 0 {
self.activeIndex = Int(draggingItem)
}
print(self.activeIndex)
}
}
)
}
func distance(_ item: Int) -> Double {
return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count))
}
func myXOffset(_ item: Int) -> Double {
let angle = Double.pi * 2 / Double(store.items.count) * distance(item)
return sin(angle) * 200
}
}