iPhone Duo Faltanimation: Scharniersensor, AGSL Shader und der Nachbau auf dem Galaxy Z Fold 8
Die iPhone Duo Faltanimation ist ein Übergang in der Benutzeroberfläche von Apples erstem faltbaren Telefon, vorgestellt am 9. September 2026, im Handel ab dem 23. Oktober 2026. Beim Öffnen und Schließen werden Hintergrund und Oberfläche zunehmend unscharf, dunkler und scheinen durch die bewegte Displayhälfte zu wandern, bevor sie wieder klar werden. Der Übergang läuft nicht gegen eine Uhr, sondern gegen den gemessenen Scharnierwinkel. Deshalb folgt er der Hand statt einem Timer.
Einen Tag nach der Vorstellung veröffentlichte ein Entwickler einen funktionierenden Nachbau auf einem Samsung Galaxy Z Fold 8: eine eigenständige Android App, die den Scharniersensor liest, über die Presentation API auf beiden Displays rendert und einen AGSL Runtime Shader mit dem aktuellen Scharnierwert steuert. Die Demo nutzt Screenshots der inneren und äußeren Startbildschirme statt des echten Launchers. Der Entwickler hat das offen gesagt, die Presse hat es wiederholt.
1. Hardware
Das iPhone Duo kombiniert ein 7,6 Zoll großes inneres Super Retina XDR Panel mit Nano-Texture Oberfläche und ein 5,4 Zoll großes äußeres Panel, das rund neunzig Prozent der Bildschirmfläche eines iPhone 18 Pro abdeckt. Beide bieten ProMotion und Always On mit bis zu 3.000 Nits. Der Chip ist der A20 Pro mit sechs CPU Kernen, sieben GPU Kernen und einer doppelten 16-Kern Neural Engine, gekühlt über eine Vapor Chamber. Apple nennt bis zu fünfunddreißig Prozent höhere Dauerleistung als beim iPhone 17 Pro. Das Scharnier besteht aus mehr als hundert Bauteilen, jede Hälfte trägt einen eigenen Akku. Preis ab 1.999 Dollar, Vorbestellung ab 16. Oktober 2026, Software iOS 27.1.
Zwei Punkte sind für die Animation entscheidend. Erstens GPU Reserven, denn ein Blur pro Pixel über ein 7,6 Zoll Panel bei 120 Hz ist über die Dauer einer Scharnierbewegung teuer. Zweitens das Scharnier selbst: Ein Übergang, der Geometrie abbilden will, überzeugt nur bei glatten, latenzarmen Winkelwerten. Grob quantisierte Sensordaten erzeugen ein Stufen, das kein Shader verdeckt.
2. Beobachtbares Verhalten
- Zunehmende Unschärfe konzentriert um die Falte, am stärksten in der Mitte der Bewegung.
- Abdunklung im selben Bereich, die wie ein Schatten in der Falte wirkt.
- Teilweise Transparenz, sodass die bewegte Hälfte durchscheinend wirkt.
- Perspektivische Stauchung zum Scharnier hin, während sich das Panel wegdreht.
- Eine Überblendung zwischen dem Inhalt des geschlossenen und des offenen Zustands.
Entscheidend ist die Umkehrbarkeit. Wer die Bewegung anhält, friert den Effekt im passenden Zwischenzustand ein. Wer zurückdreht, dreht den Effekt zurück. Eine zeitbasierte Animation kann das nicht, weil ihre Fortschrittsvariable nichts mit der physischen Lage des Geräts zu tun hat.
3. Der Scharnierwinkel als Steuersignal
3.1 Verfügbarkeit
Android bietet seit API Level 30 einen Scharniersensor. Sensor.TYPE_HINGE_ANGLE meldet den Winkel zwischen beiden Hälften in Grad als normalen Event Stream. Wertebereich und Ruhelage unterscheiden sich je Gerät, deshalb sollte Produktionscode sie vom Sensorobjekt lesen statt 0 bis 180 anzunehmen.
Unter macOS ist das Gegenstück der interne Deckelwinkelsensor, erreichbar als IOHIDDevice mit Vendor 0x05AC und Product 0x8104. Das Projekt macTilt pollt ihn mit 60 Hz und erlaubt konfigurierbare Start- und Endwinkel, etwa Beginn bei 80 Grad und Abschluss bei 3 Grad. Apple dokumentiert dieses Gerät nicht, der Ansatz ist also rückentwickelt.
3.2 Normalisierung
p = clamp( (theta - theta_closed) / (theta_open - theta_closed), 0, 1 )
p = 0 geschlossen, p = 0.5 halb offen, p = 1 offen. Wenn die Normalisierung an einer Stelle bleibt, enthält der Shader keine gerätespezifischen Konstanten und läuft unverändert auf Hardware mit anderem Winkelbereich.
p_smooth = p_smooth + a * (p_raw - p_smooth), a etwa 0.25
Zu viel Glättung und der Effekt hinkt der Hand hinterher, was die Illusion gründlicher zerstört als Rauschen. Auf echter Hardware abstimmen.
4. Rekonstruiertes Shader Modell
Die folgenden Formeln sind eine Rekonstruktion. Sie erzeugen den sichtbaren Effekt und decken sich mit der Beschreibung des Android Entwicklers, sind aber nicht Apples Implementierung, die nicht veröffentlicht ist.
4.1 Maske um das Scharnier
d = |x - h|
M(x) = 1 - smoothstep(0, w, d)
4.2 Zunehmende Unschärfe
B(p) = B_max * sin(pi * p)
B(x,p) = M(x) * B_max * sin(pi * p)
Geschlossen scharf, halb offen am weichsten, offen wieder scharf. Als Kalibrierung: Die Three.js Studie desselben Effekts nutzt einen maximalen Blurradius von 72 Quellpixeln und dunkelt mit doppelter Intensität ab, begrenzt auf Schwarz.
4.3 Auflösung und Transparenz
alpha(x,p) = 1 - k * M(x) * sin(pi * p)
Mit k = 0.65 bleiben unberührte Bereiche nahe alpha 1.00, der Faltbereich fällt in der Mitte auf etwa 0.35. Ein transparentes Panel existiert nicht. Es funktioniert, weil der Betrachter keine unabhängige Information darüber hat, was hinter der bewegten Hälfte liegt.
4.4 Interpolation zweier Bilder
C(x,y,p) = mix( C_outer, C_inner, t )
t = 3p^2 - 2p^3
4.5 Perspektivische Stauchung
s(theta) = |cos(theta)|
x' = h + (x - h) * s(theta)
x'' = x' + k * sin(theta)
4.6 Licht und Glanzkante
L(x,p) = 1 - lambda * M(x) * sin(pi * p)
C_lit = C * L
H(x) = exp( -(x - h)^2 / (2 * sigma^2) )
C_final = C_lit + q * H(x)
Von außen nach innen gelesen: normale Fläche, dunkle Falte, schmale helle Linie. Genau dieser Schritt fehlt den meisten Nachbauten, und deshalb wirkt ein technisch korrekter Blur oft flach.
5. Implementierung
5.1 Sensor in Kotlin lesen
fun hingeProgress(context: Context, smoothing: Float = 0.25f): Flow<Float> = callbackFlow {
val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val hinge = manager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
if (hinge == null) { close(); return@callbackFlow }
val maxAngle = hinge.maximumRange.takeIf { it > 0f } ?: 180f
var filtered = Float.NaN
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
val raw = (event.values[0] / maxAngle).coerceIn(0f, 1f)
filtered = if (filtered.isNaN()) raw else filtered + smoothing * (raw - filtered)
trySend(filtered)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
}
manager.registerListener(listener, hinge, SensorManager.SENSOR_DELAY_GAME)
awaitClose { manager.unregisterListener(listener) }
}
5.2 Der AGSL Shader
uniform shader outerImage;
uniform shader innerImage;
uniform float2 size;
uniform float progress;
uniform float hingeX;
uniform float bandWidth;
uniform float maxBlur;
uniform float alphaK;
uniform float darkK;
uniform float gleamK;
float ease(float t) { return t * t * (3.0 - 2.0 * t); }
half4 blurSample(shader img, float2 uv, float radius) {
if (radius < 0.5) return img.eval(uv);
half4 sum = half4(0.0);
float total = 0.0;
for (int i = -4; i <= 4; i++) {
float o = float(i) / 4.0;
float wgt = exp(-o * o * 2.0);
sum += img.eval(uv + float2(o * radius, 0.0)) * half(wgt);
total += wgt;
}
return sum / half(total);
}
half4 main(float2 fragCoord) {
float x = fragCoord.x / size.x;
float d = abs(x - hingeX);
float mask = 1.0 - smoothstep(0.0, bandWidth, d);
float env = sin(3.14159265 * progress);
float theta = progress * 3.14159265;
float s = abs(cos(theta * 0.5));
float xp = hingeX + (x - hingeX) * mix(1.0, s, mask);
float2 warped = float2(xp * size.x, fragCoord.y);
float radius = maxBlur * mask * env;
half4 color = mix(blurSample(outerImage, warped, radius),
blurSample(innerImage, warped, radius),
half(ease(progress)));
float light = 1.0 - darkK * mask * env;
float sigma = bandWidth * 0.35;
float gleam = exp(-(d * d) / (2.0 * sigma * sigma)) * gleamK * env;
color.rgb = color.rgb * half(light) + half(gleam);
color.a = color.a * half(1.0 - alphaK * mask * env);
return color;
}
5.3 Anbindung in Compose
val shader = remember { RuntimeShader(FOLD_AGSL) }
Box(
Modifier.fillMaxSize()
.onSizeChanged { shader.setFloatUniform("size", it.width.toFloat(), it.height.toFloat()) }
.graphicsLayer {
shader.setFloatUniform("progress", progress)
shader.setFloatUniform("hingeX", 0.5f)
shader.setFloatUniform("bandWidth", 0.18f)
shader.setFloatUniform("maxBlur", 72f)
shader.setInputShader("outerImage", ImageShader(outer))
shader.setInputShader("innerImage", ImageShader(inner))
renderEffect = RenderEffect
.createRuntimeShaderEffect(shader, "outerImage")
.asComposeRenderEffect()
clip = true
}
)
Den RuntimeShader in jedem Frame neu zu bauen ist der häufigste Performancefehler in solchem Code. Deshalb steht er hinter remember.
5.4 Zweites Display über Presentation
val dm = getSystemService(DisplayManager::class.java)
dm.displays
.firstOrNull { it.displayId != windowManager.defaultDisplay.displayId }
?.let { FoldPresentation(this, it, progressFlow).show() }
Beide Flächen lesen denselben StateFlow. Zwei unabhängig animierte Displays laufen binnen weniger Frames auseinander, und das sieht man von der Seite sofort.
6. Performance
- Nur den Faltbereich shaden, außerhalb der Maske direkt das Quellpixel zurückgeben.
- Vor dem Blur herunterskalieren, halbe Auflösung ist bei diesen Radien nicht unterscheidbar.
- Frames überspringen, wenn sich p kaum geändert hat.
- Die Screenshots einmal zu Beginn der Bewegung aufnehmen, nicht pro Frame.
7. Grenzen des Nachbaus
Viele Berichte beschrieben die Demo als Apples Funktion auf Samsung Hardware. Das stimmt nicht. Es ist eine App, die zwei aufgenommene Bilder mit einem Blur animiert, und sie lässt sich in dieser Form nicht in die SystemUI einbauen. Eine systemweite Variante bräuchte Zugriff auf jedes App Fenster, den sich nur der Plattformbetreiber selbst geben kann.
8. Das Blickwinkelproblem
Image = f( theta_hinge, theta_viewer, x, y )
Der Shader kennt den Scharnierwinkel, nicht die Augenposition. Frontal stimmen simulierte und reale Perspektive überein, seitlich laufen sie auseinander. Das ist eine Eigenschaft des Ansatzes, kein Fehler einer Implementierung.
9. Fazit
Apples Faltanimation beeindruckt nicht, weil Unschärfe schwer wäre. Sie beeindruckt, weil das Rendering so eng an der physischen Bewegung hängt, dass das Gehirn beide Displayhälften als eine durchgehende Fläche behandelt. Der Sensor ist die Uhr, der Shader ist der Renderer, und die Illusion steht und fällt mit ihrer Übereinstimmung.
10. Quellen
- Apple Newsroom, Apple unveils iPhone Duo, 9. September 2026.
- r/GalaxyFold, Nachbau der iPhone Duo Animation auf dem Fold, 9. September 2026.
- lqSky7, iphone-duo-macos-animation (macTilt), GitHub.
- chuspeeism, iphone-duo, GitHub.
- 9to5Google, 10. September 2026.
- Android Developers: Sensor, AGSL, Presentation.