Der Aufmacher überdeckt, statt zu kreuzen
Beim Kreuzblenden sind für einen Moment beide Bilder halbdurchsichtig — und dazwischen scheint der Untergrund durch. Das war das helle Aufblitzen des vorigen Titels. Jetzt bleibt das alte Bild voll deckend liegen, und das neue legt sich darüber, während es hereinrutscht. Erst wenn oben nichts mehr durchscheint, verschwindet unten etwas — die Fläche ist zu keinem Zeitpunkt durchsichtig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e7de8a9055
commit
b4c2bd3970
@@ -17,8 +17,16 @@ import MediaKit
|
||||
/// gegeneinander, und die Naht dazwischen wird zum auffälligsten Ding auf dem
|
||||
/// Bildschirm.
|
||||
///
|
||||
/// Jetzt rutscht das neue Bild nur ein kleines Stück herein und blendet dabei
|
||||
/// über das alte. Nichts wandert weit genug, als dass ein Versatz auffiele.
|
||||
/// Jetzt rutscht das neue Bild nur ein kleines Stück herein und legt sich
|
||||
/// dabei über das alte. Nichts wandert weit genug, als dass ein Versatz
|
||||
/// auffiele.
|
||||
///
|
||||
/// **Es wird nicht gekreuzt, sondern überdeckt.** Beim Kreuzblenden sind
|
||||
/// beide Bilder gleichzeitig halbdurchsichtig — und dazwischen scheint der
|
||||
/// Untergrund durch, was als helles Aufblitzen des vorigen Titels zu sehen
|
||||
/// war. Hier bleibt das alte Bild voll deckend liegen, bis das neue darüber
|
||||
/// undurchsichtig geworden ist; erst dann verschwindet es. Zu jedem Zeitpunkt
|
||||
/// ist die Fläche gedeckt.
|
||||
struct HeroCarousel: View {
|
||||
let items: [MediaItem]
|
||||
|
||||
@@ -35,6 +43,13 @@ struct HeroCarousel: View {
|
||||
private static let slide: CGFloat = 32
|
||||
|
||||
@State private var shownID: String?
|
||||
/// Der Titel, der noch darunter liegt und gleich verschwindet.
|
||||
///
|
||||
/// Er wird voll deckend gezeigt, nicht ausgeblendet — das Ausblenden
|
||||
/// übernimmt das neue Bild, indem es ihn zudeckt.
|
||||
@State private var fadingID: String?
|
||||
/// Wie weit das neue Bild schon deckt: 0 beim Beginn, 1 am Ende.
|
||||
@State private var cover: CGFloat = 1
|
||||
/// Ob noch von selbst weitergewechselt wird.
|
||||
///
|
||||
/// Hört auf, sobald jemand die Fläche berührt. Wer hier die Hand hat,
|
||||
@@ -45,8 +60,6 @@ struct HeroCarousel: View {
|
||||
/// das Bild auch von der anderen Seite hereinkommen sehen.
|
||||
@State private var isMovingForward = true
|
||||
|
||||
/// Bewegung, die von selbst losgeht, ist für manche Menschen unangenehm
|
||||
/// bis unbrauchbar. Wer sie abgestellt hat, blättert von Hand.
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@Environment(\.flixyAccent) private var accent
|
||||
@Environment(FlixySession.self) private var session
|
||||
@@ -58,24 +71,23 @@ struct HeroCarousel: View {
|
||||
var body: some View {
|
||||
VStack(spacing: FlixySpacing.regular) {
|
||||
ZStack {
|
||||
// Unten: der vorige Titel, unverändert deckend.
|
||||
if let fadingID, let previous = items.first(where: { $0.id == fadingID }) {
|
||||
HeroSection(item: previous)
|
||||
}
|
||||
|
||||
// Oben: der neue, der ihn zudeckt und dabei hereinrutscht.
|
||||
if items.indices.contains(currentIndex) {
|
||||
HeroSection(item: items[currentIndex])
|
||||
.id(items[currentIndex].id)
|
||||
.transition(.asymmetric(
|
||||
insertion: .opacity.combined(
|
||||
with: .offset(x: isMovingForward ? Self.slide : -Self.slide)
|
||||
),
|
||||
removal: .opacity.combined(
|
||||
with: .offset(x: isMovingForward ? -Self.slide : Self.slide)
|
||||
)
|
||||
))
|
||||
.opacity(cover)
|
||||
.offset(x: (1 - cover) * (isMovingForward ? Self.slide : -Self.slide))
|
||||
}
|
||||
}
|
||||
.aspectRatio(FlixyAspectRatio.hero, contentMode: .fit)
|
||||
// Das Bild wechselt, der Platz nicht: ohne das Beschneiden schöbe
|
||||
// das hereinrutschende Bild die Seite um seine Strecke breiter.
|
||||
.clipped()
|
||||
.animation(.easeInOut(duration: 0.55), value: shownID)
|
||||
// Wischen blättert weiter, jede Berührung hält das Wechseln an —
|
||||
// auch die, die nur ein Antippen wird. Gleichzeitig, damit weder
|
||||
// dem Wischen noch den Knöpfen darunter etwas weggenommen wird.
|
||||
@@ -114,8 +126,20 @@ struct HeroCarousel: View {
|
||||
private func step(by direction: Int) {
|
||||
guard items.count > 1 else { return }
|
||||
let next = (currentIndex + direction + items.count) % items.count
|
||||
|
||||
isMovingForward = direction > 0
|
||||
fadingID = shownID
|
||||
shownID = items[next].id
|
||||
cover = 0
|
||||
|
||||
withAnimation(.easeInOut(duration: 0.55)) {
|
||||
cover = 1
|
||||
} completion: {
|
||||
// Erst wenn oben nichts mehr durchscheint, darf unten etwas
|
||||
// verschwinden. Andersherum gäbe es genau den Moment, in dem man
|
||||
// durch beide hindurch auf den Untergrund sieht.
|
||||
fadingID = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func advance() async {
|
||||
|
||||
Reference in New Issue
Block a user