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.
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.
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
- Instalando Reanimated e valores compartilhados
- Worklets e execução de código na thread da interface
- Gestos de arrastar e deslizar com Gesture Handler
- Pinçar para ampliar e girar