React Native: Efeito “imagem overlay animada” estilo Instagram

Sobrepondo e animando uma imagem ao curtir uma foto

Image for post
Image for post
Sobrepondo o coração ao curtir a foto! — Créditos

Quando você curte uma imagem no Instagram, um coração rapidamente “cresce” e suavemente desaparece por cima da imagem que você curtiu — Como podemos fazer o mesmo com React Native? Nesse rápido tutorial, iremos implementar exatamente isso!

Image for post

Iniciando nosso exemplo

Esse tutorial é uma continuação do artigo anterior:

Onde implementamos um componente para detectar um tap duplo.

Agora, vamos iniciar um novo projeto com create-react-native-app:

create-react-native-app AnimatedImageOverlay
cd AnimatedImageOverlay

Faça o download dos arquivos criados no artigo anterior (nosso componente de tap duplo, todas as imagens, etc) no link seguinte:

Faça a extração do .zip para um diretório src:

# dentro de `/AnimatedImageOverlay`

mkdir src

# copie todos os arquivos para dentro dessa nova pasta

Agora, modifique o App.js criado:

import App from './src';
export default App;

Imagem de Overlay

Agora, vamos iniciar nossas modificações adicionando a imagem de overlay necessária e seus estilos.

Primeiro, vamos adicionar um método para renderizar a imagem de overlay:

# em `/src/index.js`

export default class App extends React.Component {
// ...
renderOverlay = () => {
return (
<View style={styles.overlay}>
<Image
source={require('./images/heart.png')}
style={styles.overlayHeart}
/>
</View>
);
}
// ...
}

Precisamos renderizar esse overlay. Nosso componente DoubleTap só aceita um elemento filho, por isso, precisamos envolver nossa Imagem em uma View. Felizmente, isso também ajuda com o layout:

export default class App extends React.Component {
// ...
render() {
return (
<View style={styles.container}>
<DoubleTap onDoubleTap={this.toggleLike}>
<View>
<Image
source={{ uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=${w.width}` }}
style={{ width: w.width, height: w.width }}
resizeMode="cover"
/>
{this.renderOverlay()}
</View>
</DoubleTap>
{/* ... */}
</View>
);
}
}

Chegamos ao seguinte resultado:

Image for post
Image for post

Nosso próximo passo, é dizer para o container overlay se posicionar absolutamente e ocupar todo o espaço disponível da área do componente View e centralizar seu conteúdo. Nós também vamos adicionar um tintColor para a imagem:

// ...

const styles = StyleSheet.create({
// ...
overlay: {
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
overlayHeart: {
tintColor: '#fff',
},
});

E o resultado é:

Image for post
Image for post

Animando a Imagem

Precisamos importar a funcionalidade de animação do React Native, através do pacote Animated.

Iremos criar uma nova animação no componente. Essa animação irá guiar tanto a opacidade, quando a escala da imagem, tudo graças a habilidade de interpolar os valores de animação do React Native.

import {
//...
Animated,
} from 'react-native';

export default class App extends React.Component {
// ...
animatedValue = new Animated.Value(0);
// ...
}

Ao curtir a imagem, precisamos modificar animatedValue, iniciando a animação. Para isso, precisamos modificar o método toggleLike. Se a imagem for curtida, nós iremos iniciar a sequência que trará this.animatedValue para o valor 1 e imediatamente retornar a 0.

Irei utilizar o método Animated.spring, deixando a declaração dessa sequência mais explícita:

export default class App extends React.Component {
// ...
toggleLike = () => {
this.setState((state) => {
const newLiked = !state.liked;
if (newLiked) {
Animated.sequence([
Animated.spring(this.animatedValue, { toValue: 1, useNativeDriver: true }),
Animated.spring(this.animatedValue, { toValue: 0, useNativeDriver: true }),
]).start();
}
return { liked: newLiked };
});
}
// ...
}

A última coisa que precisamos fazer é modificar renderOverlay para utilizar o valor da nossa animação para modificar seus estilos. Iremos substituir o componente Image pelo Animated.Image, dessa maneira, podemos interpolar os valores da animação e o componente saberá como se comportar.

Vamos adicionar um array de estilos para nossa nova imagem. Primeiro, iremos passar nosso styles.overlayHeart e em seguida, nossos objetos dinâmicos baseados na animação.

A opacidade irá de 0 até 1, então podemos passar o valor da animação diretamente para a propriedade opacity. Porém, ao utilizar a propriedade scale, nós precisamos iniciar a animação com a imagem um pouco menor e continuar até que ela cresça para uma escala um pouco maior do seu tamanho original. Iremos especificar esses valores utilizando a API this.animatedValue.interpolate.

Veja o código a seguir:

export default class App extends React.Component {
// ...
renderOverlay = () => {
const imageStyles = [
styles.overlayHeart,
{
opacity: this.animatedValue,
transform: [
{
scale: this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0.7, 1.5],
}),
},
],
},
];
return (
<View style={styles.overlay}>
<Animated.Image
source={require('./images/heart.png')}
style={imageStyles}
/>
</View>
);
}
// ...
}

E chegamos no resultado:

Image for post

Você pode encontrar o exemplo completo nesse repositório.

⭐️ Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store