React Native: Efeito “double tap” estilo Instagram

Como tratar e detectar o evento de “dois taps”

Image for post
Image for post
Um "like" para cada dois taps! — Créditos

Existem várias maneiras de detectar o evento tap em React Native — mas como detectar um tap duplo? O evento de tap duplo tem várias funcionalidades — curtir uma foto, retornar o scroll ao topo da aba, etc. Vamos criar um componente para detectar o evento de dois taps.

Image for post

Iniciando nosso exemplo

Vamos criar um projeto exemplo com create-react-native-app:

create-react-native-app IGStyleDoubleTap

Em seguida, iremos criar:

cd IGStyleDoubleTap
mkdir src
mkdir src/images
touch src/index.js

Você vai precisar fazer o download dos ícones de coração desse link e colocar todos eles em src/images.

Em seguida, iremos atualizar src/index.js para:

import React from "react";
import {
Dimensions,
Image,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";

const w = Dimensions.get("window");

export default class App extends React.Component {
state = {
liked: false
};

toggleLike = () => this.setState(state => ({ liked: !state.liked }));

render() {
return (
<View style={styles.container}>
<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"
/>
<View style={styles.iconRow}>
<TouchableOpacity onPress={this.toggleLike}>
<Image
source={
this.state.liked
? require("./images/heart.png")
: require("./images/heart-outline.png")
}
style={styles.heartIcon}
resizeMode="cover"
/>
</TouchableOpacity>
</View>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
iconRow: {
flexDirection: "row",
alignSelf: "stretch",
marginTop: 10,
paddingVertical: 5,
paddingHorizontal: 15
},
heartIcon: {
width: 20,
height: 20
}
});

E finalmente, alteramos o App.js para:

import App from './src';

export default App;

Agora, vamos iniciar nosso projeto, fica a sua preferência entre:

  • iOS: yarn run ios
  • Android: yarn run android

Você pode curtir a imagem pressionando o ícone de coração abaixo da imagem.

Image for post
Image for post
App exemplo rodando, vamos ao "double tap"!

Tudo pronto, agora podemos começar a implementação.

Detectando o Tap Duplo

Nosso código é inspirado por esse gist criar pelo Bruno Tavares.

Primeiro, iremos envolver nosso componente Image em um componente TouchableWithoutFeedback, nos permitindo detectar eventos de tap.

No nosso componente, nós iremos guardar quando o último tap foi feito, com a variável lastTap. Perceba que nós não estamos utilizando o estado do componente aqui, ou seja, isso não irá afetar a renderização de nenhuma maneira. Colocaremos seu valor padrão para null.

export default class App extends React.Component {
// ...
lastTap = null;
// ...
}

Em seguida, iremos criar a função handleDoubleTap que irá comparar o tap atual com o último tap e, se estiver dentro do limite de tempo que configurarmos, irá chamar a função desejada.

Nós iremos utilizar Date.now() para comparar valores. Date.now() retorna os milissegundos passados desde 1 de Janeiro de 1970, 00:00:00 UTC, no nosso caso, irá funcionar perfeitamente, pois nosso efeito é baseado em milissegundos.

Em caso de sucesso, iremos invocar a função this.toggleLike(), caso contrário, iremos apenas atualizar o valor de this.lastTap com o Date.now().

export default class App extends React.Component {
// ...
lastTap = null;
handleDoubleTap = () => {
const now = Date.now();
const DOUBLE_PRESS_DELAY = 300;
if (this.lastTap && (now - this.lastTap) < DOUBLE_PRESS_DELAY) {
this.toggleLike();
} else {
this.lastTap = now;
}
}
// ...
}

Agora, precisamos envolver nosso componente Image em um TouchableWithoutFeedback (verifique se está importando corretamente do React Native) para detectar os taps:

render() {
return (
{/* ... */}
<TouchableWithoutFeedback onPress={this.handleDoubleTap}>
<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"
/>
</TouchableWithoutFeedback>
{/* ... */}
);
}

E o resultado é o seguinte:

Image for post

Isolando a funcionalidade de tap duplo em um único componente

O que acabamos de criar, funciona perfeitamente, porém, é difícil de reutilizar. Vamos refatorar nosso código e isolar essa funcionalidade em seu próprio componente:

Iremos criar:

touch src/DoubleTap.js

E adicionar:

import React from 'react';
import { TouchableWithoutFeedback } from 'react-native';

export default class DoubleTap extends React.Component {
render() {}
};

Vamos começar a mover a lógica de src/index.js. Iremos começar pela função render:

import React from "react";
import { TouchableWithoutFeedback } from "react-native";

export default class DoubleTap extends React.Component {
render() {
return (
<TouchableWithoutFeedback onPress={this.handleDoubleTap}>
{this.props.children}
</TouchableWithoutFeedback>
);
}
}

A única diferença aqui é que estamos renderizando this.props.children dentro do TouchableWithoutFeedback, podendo passar qualquer componente.

Agora, vamos mover handleDoubleTap:

export default class DoubleTap extends React.Component {
lastTap = null;

handleDoubleTap = () => {
const now = Date.now();
if (this.lastTap && now - this.lastTap < this.props.delay) {
this.props.onDoubleTap();
} else {
this.lastTap = now;
}
};

// ...
}

Você pode perceber duas diferenças aqui. Primeiro, estamos acessando o delay através de this.props.delay. Dessa maneira, quem estiver utilizando esse componente pode passar o tempo desejado. Segundo, nós estamos chamando this.props.onDoubleTap(), quem estiver utilizando esse componente pode passar a função de sucesso desejada.

Finalmente, nós iremos colocar alguns valores padrões para que nosso componente funcione sem problemas:

export default class DoubleTap extends React.Component {
static defaultProps = {
delay: 300,
onDoubleTap: () => null
};

// ...
}

Agora, vamos usar nosso novo componente em src/index.js:

import DoubleTap from './DoubleTap';

// ...

render() {
return (
{/* ... */}
<DoubleTap onDoubleTap={this.toggleLike}>
<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"
/>
</DoubleTap>
{/* ... */}
);
}

Hooray!! 🎉🎉🎉 Agora nós temos um componente reutilizável para eventos de tap duplo!

Implementamos o efeito “double tap” estilo do Instagram, ao mesmo tempo que refatoramos nossa implementação e isolamos a lógica de nosso componente.

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