Animação de dobra do iPhone Duo: sensor de dobradiça, shader AGSL e a recriação no Galaxy Z Fold 8
A animação de dobra do iPhone Duo é uma transição de interface do primeiro celular dobrável da Apple, anunciado em 9 de setembro de 2026 e à venda a partir de 23 de outubro de 2026. Ao abrir ou fechar o aparelho, o papel de parede e a interface desfocam, escurecem e parecem atravessar a metade móvel da tela antes de voltar ao foco. A transição não avança contra um relógio, e sim contra o ângulo medido da dobradiça. É por isso que ela acompanha a mão em vez de um temporizador.
Um dia depois do anúncio, um desenvolvedor publicou uma recriação funcional em um Samsung Galaxy Z Fold 8: um app Android independente que lê o sensor da dobradiça, desenha nas duas telas pela API Presentation e alimenta um shader AGSL com o valor atual da dobradiça. A demo usa capturas das telas iniciais interna e externa em vez do launcher real, limitação que o próprio desenvolvedor apontou.
1. Contexto de hardware
O iPhone Duo combina um painel interno Super Retina XDR de 7,6 polegadas com acabamento nano-texturizado e um painel externo de 5,4 polegadas que cobre cerca de noventa por cento da área de tela de um iPhone 18 Pro. Ambos têm ProMotion e Always On com até 3.000 nits. O chip A20 Pro traz seis núcleos de CPU, sete de GPU e um Neural Engine duplo de 16 núcleos, com câmara de vapor e, segundo a Apple, até trinta e cinco por cento mais desempenho sustentado que o iPhone 17 Pro. A dobradiça usa mais de cem componentes e cada metade tem bateria própria. A partir de 1.999 dólares, pré-venda em 16 de outubro de 2026, iOS 27.1.
Dois pontos importam para a animação: folga sustentada de GPU, porque um desfoque por pixel em um painel de 7,6 polegadas a 120 Hz custa caro ao longo de todo o movimento, e a dobradiça em si, porque uma transição que promete acompanhar a geometria só convence com ângulo suave e de baixa latência.
2. Comportamento observável
- Desfoque progressivo concentrado perto da dobra, máximo no meio do movimento.
- Escurecimento da mesma faixa, lido como sombra no vinco.
- Transparência parcial da metade em movimento.
- Compressão em perspectiva rumo à dobradiça enquanto o painel gira.
- Fusão cruzada entre o conteúdo fechado e o aberto.
O ponto central é a reversibilidade. Parar no meio congela o efeito no estado correspondente; inverter o gesto inverte o efeito. Uma animação por tempo não faz isso, porque sua variável de progresso não tem relação com a posição física do aparelho.
3. O ângulo como sinal de controle
O Android expõe um sensor de dobradiça desde o nível de API 30. Sensor.TYPE_HINGE_ANGLE devolve o ângulo entre as duas metades em graus. A faixa varia entre aparelhos, então código de produção lê isso do objeto do sensor em vez de supor 0 a 180.
No macOS o equivalente é o sensor interno de ângulo da tampa, acessível como IOHIDDevice com vendor 0x05AC e product 0x8104. O projeto macTilt consulta a 60 Hz com ângulos de início e fim configuráveis, por exemplo 80 e 3 graus. A Apple não documenta esse dispositivo.
p = clamp( (theta - theta_closed) / (theta_open - theta_closed), 0, 1 )
p_smooth = p_smooth + a * (p_raw - p_smooth), a perto de 0.25
Suavização demais e o efeito atrasa em relação à mão, o que estraga a ilusão mais do que o ruído.
4. Modelo de shader reconstruído
As fórmulas abaixo são uma reconstrução. Elas reproduzem o efeito visível e batem com a descrição do desenvolvedor Android, mas não são a implementação da Apple, que não foi publicada.
d = |x - h|
M(x) = 1 - smoothstep(0, w, d)
B(x,p) = M(x) * B_max * sin(pi * p)
alpha = 1 - k * M(x) * sin(pi * p)
t = 3p^2 - 2p^3
C = mix( C_outer, C_inner, t )
s(th) = |cos(th)|
x' = h + (x - h) * s(th)
L(x,p) = 1 - lambda * M(x) * sin(pi * p)
H(x) = exp( -(x - h)^2 / (2 * sigma^2) )
C_final = Blur( Transform(C, th), B ) * alpha * L + q * H(x)
Para calibrar, o estudo em Three.js do mesmo efeito usa raio máximo de desfoque de 72 pixels de origem e escurece com o dobro da intensidade, limitado ao preto. Com k = 0,65 a área intacta fica perto de alfa 1,00 e a faixa da dobra cai para cerca de 0,35.
Lido de fora para dentro: superfície normal, vinco escuro, linha brilhante estreita. Esse é o passo que quase toda recriação caseira pula, e costuma ser o motivo de um desfoque tecnicamente correto parecer chapado.
5. Implementação
5.1 Lendo o sensor em Kotlin
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 O shader AGSL
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 Compose e segunda tela
val shader = remember { RuntimeShader(FOLD_AGSL) } // nunca recriar por frame
val dm = getSystemService(DisplayManager::class.java)
dm.displays
.firstOrNull { it.displayId != windowManager.defaultDisplay.displayId }
?.let { FoldPresentation(this, it, progressFlow).show() }
As duas superfícies leem o mesmo StateFlow, então existe um único valor de progresso no sistema. Duas telas animadas de forma independente saem de sincronia em poucos quadros, e isso aparece olhando a lateral.
5.4 Equivalentes em macOS e web
A estrutura se transfere. O macTilt consulta o sensor de ângulo a 60 Hz, captura a área de trabalho com ScreenCaptureKit e desenha a dobra com Metal Shading Language. Na web não há dobradiça, então o estudo em Three.js troca o sensor por um controle deslizante e mantém o resto idêntico. Só o primeiro estágio do pipeline depende da plataforma.
6. Desempenho
- Sombrear apenas a faixa e devolver o pixel de origem fora da máscara.
- Reduzir a resolução antes do desfoque; nesses raios metade é indistinguível.
- Pular quadros quando p praticamente não mudou.
- Capturar as telas uma vez no início do movimento, não a cada quadro.
7. Limitações da recriação
Boa parte da cobertura descreveu a demo como o recurso da Apple chegando à Samsung. Não é exato. É um app que anima duas capturas com desfoque e, nessa forma, não pode ser integrado à SystemUI. Uma versão de sistema exigiria que cada janela entregasse seu conteúdo ao mesmo estágio de composição, acesso que só o dono da plataforma pode conceder a si mesmo.
8. O problema do ângulo de visão
Image = f( theta_hinge, theta_viewer, x, y )
O shader conhece o ângulo da dobradiça, não onde estão os olhos. De frente, perspectiva simulada e real coincidem; de lado, divergem. É uma propriedade da abordagem, não um defeito de implementação. Corrigir exigiria rastreamento facial e projeção por observador.
9. Conclusão
A animação da Apple não impressiona porque desfoque seja difícil. Impressiona porque a renderização está sincronizada com o movimento físico a ponto de o cérebro tratar as duas metades da tela como uma superfície contínua. O sensor é o relógio, o shader é o renderizador, e a ilusão depende de quão bem os dois concordam.
10. Referências
- Apple Newsroom, Apple unveils iPhone Duo, 9 de setembro de 2026.
- r/GalaxyFold, recriação da animação do iPhone Duo, 9 de setembro de 2026.
- lqSky7, iphone-duo-macos-animation (macTilt), GitHub.
- chuspeeism, iphone-duo, GitHub.
- 9to5Google, 10 de setembro de 2026.
- Android Developers: Sensor, AGSL, Presentation.