All Articles
8 minUpdated

Animação de dobra do iPhone Duo: sensor de dobradiça, shader AGSL e a recriação no Galaxy Z Fold 8

Most RecentTrendingAndroid

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.

Figura. A transição de dobra gravada no aparelho, 720 por 1280 a 30 fps. Repare na faixa do vinco: o desfoque atinge o máximo perto do meio e some nas duas posições de repouso, a envoltória sin(πp) da seção 4.

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

  1. Desfoque progressivo concentrado perto da dobra, máximo no meio do movimento.
  2. Escurecimento da mesma faixa, lido como sombra no vinco.
  3. Transparência parcial da metade em movimento.
  4. Compressão em perspectiva rumo à dobradiça enquanto o painel gira.
  5. 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

  1. Apple Newsroom, Apple unveils iPhone Duo, 9 de setembro de 2026.
  2. r/GalaxyFold, recriação da animação do iPhone Duo, 9 de setembro de 2026.
  3. lqSky7, iphone-duo-macos-animation (macTilt), GitHub.
  4. chuspeeism, iphone-duo, GitHub.
  5. 9to5Google, 10 de setembro de 2026.
  6. Android Developers: Sensor, AGSL, Presentation.