React Native Academy · Aula

Pinçar para ampliar e girar

Combine PinchGestureHandler e RotationGestureHandler com o reconhecimento simultâneo de gestos para implementar um componente de imagem que possa ser ampliado e girado.

Aula 4 de 413 etapas

Pinçar para ampliar e girar é uma aula grátis de React Native Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de React Native Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de React Native Academy inclui 4 aulas no total.

Por que Pinch e Rotation são importantes

O zoom com pinça e a rotação são interações clássicas em dispositivos móveis, presentes em visualizadores de fotos, mapas e aplicativos de documentos. Implementá-los bem exige reconhecimento de gestos multitoque executado na thread nativa e atualizações suaves de transformações em tempo real por meio de valores compartilhados do Reanimated.

O React Native Gesture Handler fornece os reconhecedores Gesture.Pinch() e Gesture.Rotation() que, combinados com useAnimatedStyle e useSharedValue do Reanimated, produzem interações extremamente suaves, indistinguíveis das implementações nativas.

Gesture.Pinch: detectando a escala

Gesture.Pinch() reconhece um gesto de pinça ou abertura com dois dedos. O valor mais importante no objeto de evento é event.scale, que começa em 1,0 quando o gesto é iniciado e varia proporcionalmente à distância entre os dedos. Um valor de 2,0 significa que os dedos estão duas vezes mais afastados do que no início.

Você precisa combinar a escala incremental do gesto com uma escala base armazenada para acumular o zoom entre vários gestos de pinça; caso contrário, cada nova pinça será redefinida para 1,0.

import { Gesture } from 'react-native-gesture-handler';
import { useSharedValue, withSpring } from 'react-native-reanimated';

const scale = useSharedValue(1);
const savedScale = useSharedValue(1);

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    scale.value = savedScale.value * e.scale;
  })
  .onEnd(() => {
    savedScale.value = scale.value;
  });

Limitando o intervalo do zoom

Sem limitar os valores, os usuários podem ampliar para escalas enormes ou reduzir o zoom até a imagem desaparecer. Defina um nível mínimo e máximo de zoom e limite o valor da escala no retorno de chamada onUpdate usando um worklet utilitário.

A função de limite é executada na thread da interface como um worklet, mantendo rápida a resposta ao gesto. Escolha valores adequados ao seu conteúdo: os visualizadores de fotos normalmente permitem zoom de 1x a 5x, enquanto os mapas podem permitir intervalos muito mais amplos.

const MIN_SCALE = 1;
const MAX_SCALE = 5;

function clamp(value, min, max) {
  'worklet';
  return Math.min(Math.max(value, min), max);
}

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    scale.value = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
  })
  .onEnd(() => {
    savedScale.value = scale.value;
    // Snap back if over-zoomed out:
    if (scale.value < MIN_SCALE) {
      scale.value = withSpring(MIN_SCALE);
      savedScale.value = MIN_SCALE;
    }
  });

Gesture.Rotation: acompanhando o ângulo

Gesture.Rotation() acompanha a variação do ângulo de um gesto de torção com dois dedos. O valor event.rotation é expresso em radianos: o ângulo pelo qual os dedos giraram desde a posição inicial do gesto. Uma rotação completa no sentido horário corresponde aproximadamente a 6,28 radianos (2π).

Assim como acontece com a escala, você precisa acumular a rotação entre vários gestos, salvando a rotação atual ao final do gesto e adicionando a rotação do novo gesto ao deslocamento salvo nos gestos seguintes.

const rotation = useSharedValue(0);
const savedRotation = useSharedValue(0);

const rotationGesture = Gesture.Rotation()
  .onUpdate((e) => {
    rotation.value = savedRotation.value + e.rotation;
  })
  .onEnd(() => {
    savedRotation.value = rotation.value;
  });

Aplicando escala e rotação em useAnimatedStyle

A matriz transform em useAnimatedStyle aplica tanto a escala quanto a rotação. A ordem das transformações é importante: aplique primeiro a escala e depois a rotação, ou vice-versa, dependendo do comportamento desejado. A matriz de transformações do React Native aceita diretamente valores numéricos de escala e valores de rotação em radianos provenientes de valores compartilhados.

A transformação rotate exige uma string com o sufixo 'rad', enquanto scale recebe diretamente um número. Use concatenação de strings, e não literais de modelo com crases, para construir a string de rotação em um worklet.

const animatedStyle = useAnimatedStyle(() => ({
  transform: [
    { scale: scale.value },
    { rotate: rotation.value + 'rad' }, // concatenate string, no backticks
  ],
}));

return (
  <GestureDetector gesture={composedGesture}>
    <Animated.Image
      source={{ uri: imageUrl }}
      style={[styles.image, animatedStyle]}
    />
  </GestureDetector>
);

Combinando Pinch e Rotation simultaneamente

Um visualizador de fotos precisa permitir que pinça e rotação funcionem ao mesmo tempo. Envolva os dois gestos em Gesture.Simultaneous e passe o gesto combinado para um único GestureDetector. Isso permite que o usuário amplie e gire a imagem com um gesto de dois dedos, em um movimento contínuo.

Adicionar o deslocamento simultaneamente cria a experiência completa de um visualizador de fotos: ampliar, girar e reposicionar a imagem, tudo com dois dedos ao mesmo tempo.

const composedGesture = Gesture.Simultaneous(
  pinch,
  rotationGesture,
  pan
);

return (
  <GestureDetector gesture={composedGesture}>
    <Animated.Image
      source={{ uri: imageUrl }}
      style={[styles.image, animatedStyle]}
      resizeMode='contain'
    />
  </GestureDetector>
);

Toque duplo para redefinir o zoom

Um recurso padrão de visualizadores de fotos é tocar duas vezes para alternar entre o tamanho natural e uma visualização ampliada e, ao tocar novamente, redefinir a visualização. Use Gesture.Tap().numberOfTaps(2) para o reconhecedor de toque duplo e, no manipulador, anime a escala e a rotação de volta aos valores iniciais com uma mola.

Use Gesture.Exclusive com o gesto de toque duplo tendo prioridade sobre o toque simples para evitar que o toque simples seja acionado durante uma sequência de toque duplo.

const doubleTap = Gesture.Tap()
  .numberOfTaps(2)
  .onEnd(() => {
    if (scale.value > 1) {
      // Reset to natural size:
      scale.value = withSpring(1);
      savedScale.value = 1;
      rotation.value = withSpring(0);
      savedRotation.value = 0;
      offsetX.value = withSpring(0);
      offsetY.value = withSpring(0);
    } else {
      // Zoom to 2x:
      scale.value = withSpring(2);
      savedScale.value = 2;
    }
  });

Ponto de pivô do zoom com Pinch

Por padrão, a transformação de escala amplia a imagem a partir do ponto central. Para obter uma sensação mais natural, a imagem deve ser ampliada ao redor do ponto focal — o ponto médio entre os dois dedos. O evento do gesto fornece event.focalX e event.focalY para essa finalidade.

Implementar o zoom pelo ponto focal exige ajustar o deslocamento junto com a escala, para que o ponto sob os dedos permaneça fixo durante o zoom. Esse cálculo envolve determinar a alteração do deslocamento causada pela mudança de escala em um determinado ponto focal.

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    const newScale = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
    const scaleDelta = newScale / scale.value;
    // Adjust offset to keep focal point fixed:
    offsetX.value = e.focalX - scaleDelta * (e.focalX - offsetX.value);
    offsetY.value = e.focalY - scaleDelta * (e.focalY - offsetY.value);
    scale.value = newScale;
  });

Restrições de limites após deslocar e ampliar

Depois de ampliar a imagem, o usuário pode deslocá-la para ver partes diferentes. No entanto, você deve restringir esse deslocamento para impedir que a imagem seja arrastada e revele a tela vazia além de suas bordas. Calcule o deslocamento máximo com base na escala atual e nas dimensões da imagem e limite o deslocamento no onUpdate do gesto de deslocamento.

É esse refinamento que diferencia um visualizador de fotos profissional de um protótipo rudimentar. A lógica de restrição consiste em algumas linhas de cálculos executadas como worklets na thread da interface.

const IMAGE_WIDTH = 300;
const CONTAINER_WIDTH = 375;

const pan = Gesture.Pan()
  .onUpdate((e) => {
    const maxOffsetX = Math.max(0, (scale.value * IMAGE_WIDTH - CONTAINER_WIDTH) / 2);
    offsetX.value = clamp(
      savedOffsetX.value + e.translationX,
      -maxOffsetX,
      maxOffsetX
    );
  });

Animando o retorno após ampliar além do limite

Quando o usuário tira os dedos da tela após ampliar além dos limites, abaixo do mínimo ou acima do máximo, faça a escala retornar com uma mola ao limite correspondente, em vez de deixar a imagem em um tamanho incomum. Faça isso no retorno de chamada onEnd do gesto de pinça.

Da mesma forma, depois que o usuário reduzir o zoom de volta para 1x, redefina o deslocamento para zero para que a imagem retorne ao centro. Essa combinação de verificações de limites em onEnd faz com que o componente pareça refinado e previsível.

const pinch = Gesture.Pinch()
  .onEnd(() => {
    if (scale.value < MIN_SCALE) {
      scale.value = withSpring(MIN_SCALE);
      savedScale.value = MIN_SCALE;
      // Reset pan to center:
      offsetX.value = withSpring(0);
      offsetY.value = withSpring(0);
    } else if (scale.value > MAX_SCALE) {
      scale.value = withSpring(MAX_SCALE);
      savedScale.value = MAX_SCALE;
    } else {
      savedScale.value = scale.value;
    }
  });

Combinando tudo

Um visualizador de fotos completo, com zoom, rotação e deslocamento, combina: valores compartilhados para escala, rotação e deslocamento; useAnimatedStyle aplicando todas as transformações; Gesture.Simultaneous combinando pinça, rotação, deslocamento e toque duplo; e limitação nos limites em onUpdate, com correção por mola em onEnd.

Essa é uma das interações mais impressionantes que você pode criar no React Native, e ela é executada inteiramente na thread da interface, sem envolver a thread do JavaScript durante o gesto — tornando-a indistinguível de uma implementação nativa.

const composed = Gesture.Simultaneous(
  Gesture.Exclusive(
    doubleTap,
    singleTap
  ),
  pan,
  pinch,
  rotationGesture
);

const style = useAnimatedStyle(() => ({
  transform: [
    { translateX: offsetX.value },
    { translateY: offsetY.value },
    { scale: scale.value },
    { rotate: rotation.value + 'rad' },
  ],
}));

Verificação rápida

Teste sua compreensão dos conceitos de desenvolvimento móvel com React Native apresentados nesta lição.

Recapitulação da lição

Nesta lição, você aprendeu: Gesture.Pinch() fornece event.scale para acompanhar o zoom com dois dedos em relação ao início do gesto, Gesture.Rotation() fornece event.rotation em radianos para acompanhar a torção com dois dedos e Gesture.Simultaneous combina pinça, rotação e deslocamento para que os três funcionem ao mesmo tempo, proporcionando a experiência completa de um visualizador de fotos. A seguir, configuraremos esquemas de URL personalizados para criar links diretos para telas específicas do aplicativo.

Grátis para começar

Aprenda JavaScript com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
30
Aulas
120

Perguntas Frequentes

A aula “Pinçar para ampliar e girar” é grátis?

Sim — o texto completo de “Pinçar para ampliar e girar” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de React Native Academy, atualize para CoddyKit PRO. O curso de React Native Academy inclui 4 aulas no total.

O que vou aprender em “Pinçar para ampliar e girar”?

Combine PinchGestureHandler e RotationGestureHandler com o reconhecimento simultâneo de gestos para implementar um componente de imagem que possa ser ampliado e girado. Você pratica React Native Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar React Native Academy?

Nenhuma experiência prévia é necessária. React Native Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Pinçar para ampliar e girar”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de React Native Academy?

Sim. Cada aula de React Native Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Instalando Reanimated e valores compartilhados
  2. Worklets e execução de código na thread da interface
  3. Gestos de arrastar e deslizar com Gesture Handler
  4. Pinçar para ampliar e girar
← Voltar para React Native Academy