Der Aufmacher hellte beim Wechsel auf — zwei Verläufe ergeben keinen
Beim Wechsel im Karussell war der Verlauf nach unten für einen Moment
weg: das Bild stand bis unten hell da, und erst nach dem Übergang kam das
Abdunkeln zurück.
**Der Grund steckt in der Form der Überblendung.** Das Karussell legte
zwei vollständige Aufmacher übereinander und blendete den oberen ein. Nur
bringt jeder von beiden seinen eigenen Verlauf mit — und zwei
halbdurchsichtige Verläufe ergeben zusammen keinen ganzen.
Gemessen, an derselben Stelle im Bild, über einen auf sechs Sekunden
gestreckten Wechsel:
Ruhezustand 68 .. 75
Mitte des Wechsels 137 ← fast doppelt so hell
Zwei naheliegende Reparaturen habe ich davor versucht und verworfen,
weil die Messung sie widerlegt hat: dem weichenden Aufmacher die
Animation zu nehmen (wurde schlechter, 152) und beide Ebenen
gegenläufig zu blenden (brachte einen dunklen Einbruch auf 35 dazu).
Beide behandelten das Symptom, nicht die Form.
**Jetzt wechselt nur das Bild.** `HeroBanner` nimmt zusätzlich die
weichende Bildadresse entgegen und blendet **innen** über; Verlauf,
Schriftzug und Knöpfe liegen einmal darüber und ändern sich dabei nicht.
Damit kann der Verlauf gar nicht mehr schwächer werden — nicht weil es
jetzt richtig gerechnet wird, sondern weil es nur noch einen gibt.
Nachgemessen, derselbe Wechsel:
vorher 68 .. 154
jetzt 63 .. 75
Die Aufmacher-Ansicht behält dabei ihre Identität, statt je Titel eine
neue zu bekommen: eine neue hiesse Einfügen und Entfernen, und SwiftUI
blendete sie dann zusätzlich ein — noch eine Überblendung über der
gemeinten.
iOS und tvOS bauen, 476 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a665d79e6d
commit
63c664e5a9
+4879
-4880
File diff suppressed because it is too large
Load Diff
@@ -87,26 +87,39 @@ struct HeroCarousel: View {
|
||||
sizeClass == .compact ? 0 : chrome.inset
|
||||
}
|
||||
|
||||
/// Der Aufmacher — ein einziger, der sein Bild wechselt.
|
||||
///
|
||||
/// **Nicht zwei übereinander.** So war es, und jeder der beiden brachte
|
||||
/// seinen eigenen Verlauf nach unten mit; zwei halbdurchsichtige Verläufe
|
||||
/// ergeben zusammen keinen ganzen. Gemessen an derselben Stelle im Bild:
|
||||
/// im Ruhezustand 68, in der Mitte des Wechsels 137 — sichtbar als
|
||||
/// Aufhellen genau dort, wo der Titel steht. Jetzt wechselt nur das Bild,
|
||||
/// Verlauf und Text liegen einmal darüber.
|
||||
@ViewBuilder
|
||||
private var heroSection: some View {
|
||||
if items.indices.contains(currentIndex) {
|
||||
HeroSection(
|
||||
item: items[currentIndex],
|
||||
outgoing: fadingID.flatMap { kennung in
|
||||
items.first { $0.id == kennung }
|
||||
},
|
||||
crossfade: cover,
|
||||
incomingOffset: (1 - cover) * (isMovingForward ? Self.slide : -Self.slide)
|
||||
)
|
||||
// Ohne eigene Identität je Titel: die Ansicht **bleibt**, und nur
|
||||
// ihre Angaben wechseln. Eine neue Identität hiesse, sie würde
|
||||
// eingefügt und entfernt — und SwiftUI blendete sie dabei ein,
|
||||
// zusätzlich zur Überblendung, die hier gemeint ist.
|
||||
}
|
||||
}
|
||||
|
||||
private var currentIndex: Int {
|
||||
items.firstIndex { $0.id == shownID } ?? 0
|
||||
}
|
||||
|
||||
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)
|
||||
.opacity(cover)
|
||||
.offset(x: (1 - cover) * (isMovingForward ? Self.slide : -Self.slide))
|
||||
}
|
||||
}
|
||||
heroSection
|
||||
// Die Höhe: fast das ganze Fenster.
|
||||
//
|
||||
// Ein Aufmacher, der nur ein Drittel hoch ist, sieht aus wie ein
|
||||
|
||||
@@ -5,6 +5,12 @@ import MediaKit
|
||||
/// Der Aufmacher oben auf der Startseite.
|
||||
struct HeroSection: View {
|
||||
let item: MediaItem
|
||||
/// Der Titel, dessen Bild gerade verschwindet — nur im Karussell gesetzt.
|
||||
var outgoing: MediaItem?
|
||||
/// Wie weit das neue Bild schon deckt.
|
||||
var crossfade: CGFloat = 1
|
||||
/// Wie weit es dabei hereinrutscht.
|
||||
var incomingOffset: CGFloat = 0
|
||||
|
||||
@Environment(FlixySession.self) private var session
|
||||
@Environment(PlaybackCoordinator.self) private var playback
|
||||
@@ -20,6 +26,11 @@ struct HeroSection: View {
|
||||
artworkURLs: session.backend.map {
|
||||
HeroArtwork.backdropURLs(for: item, backend: $0, compact: sizeClass == .compact)
|
||||
} ?? [],
|
||||
outgoingArtworkURLs: session.backend.flatMap { backend in
|
||||
outgoing.map { HeroArtwork.backdropURLs(for: $0, backend: backend, compact: sizeClass == .compact) }
|
||||
} ?? [],
|
||||
crossfade: crossfade,
|
||||
incomingOffset: incomingOffset,
|
||||
logoURL: session.backend.flatMap { HeroArtwork.logoURL(for: item, backend: $0) },
|
||||
// Auf dem iPhone hochkant: 16:9 ließ dem Text so wenig Platz, dass
|
||||
// Schriftzug und Beschreibung übereinanderlagen.
|
||||
|
||||
Reference in New Issue
Block a user