React Native: faça uma lista arrastável e deslizável

Hoje, é difícil surpreender qualquer pessoa com a capacidade de deslizar itens da lista em aplicativos para dispositivos móveis. Um de nossos aplicativos nativos a reagentes também tinha essa funcionalidade, mas recentemente houve a necessidade de expandi-lo com a capacidade de arrastar e soltar itens da lista. E como o processo de encontrar uma solução me custou uma certa quantidade de células nervosas, decidi arquivar um pequeno artigo para economizar um tempo valioso para as gerações futuras.



Em nosso aplicativo, usamos o pacote react react-native-swipe-list-view para criar a lista swipeable. O primeiro pensamento foi pegar um pacote com a funcionalidade drag'n'drop e atravessar o ouriço com uma cobra.

Uma pesquisa na Internet deu três candidatos: react-native-draggable-flatlist .

Usando o primeiro pacote, não foi possível executar nem o exemplo em anexo (no entanto, não apenas para mim, o problema correspondente é indicado nos problemas ).

Eu tive que mexer no segundo pacote, mas consegui criar uma lista arrastável e deslizante. No entanto, o resultado não foi inspirado - o componente estava descaradamente piscando: piscamento do redesenho, queda de elementos muito além da lista ou mesmo seu desaparecimento. Tornou-se claro que, desta forma, não pode ser usado.

A princípio, o último pacote também se comportava como uma dama caprichosa, mas depois aconteceu que eu simplesmente não sabia cozinhar. Depois de pegar a chave do coração dessa "dama", consegui alcançar um resultado aceitável.

Em nosso projeto, havia uma lista deslizante na qual você precisa prender o arraste e solte, mas, na prática, é melhor começar do outro lado: primeiro faça uma lista arrastável e depois adicione a capacidade de deslizar.

Os leitores devem saber como criar um projeto nativo de reação, portanto, vamos nos concentrar na criação da lista de que precisamos. O exemplo discutido abaixo é o código TypeScript.

Fazendo uma lista arrastável


Então, vamos começar instalando o pacote:

 yarn add react-native-draggable-flatlist 

Importamos os módulos necessários:

 import React, { Component } from 'react' import { View } from 'react-native' import styles from './styles' import DraggableFlatList, { RenderItemInfo, OnMoveEndInfo } from 'react-native-draggable-flatlist' import ListItem from './components/ListItem' import fakeData from './fakeData.json' 

Aqui DraggableFlatList é um componente do pacote instalado que implementa a ListItem arrastar e soltar, ListItem é nosso componente para exibir um item da lista (o código será apresentado abaixo), fakeData é um arquivo json que contém dados falsos - neste caso, uma matriz de objetos do formulário:

 {"id": 0, "name": "JavaScript", "favorite": false} 

Em um aplicativo real, esses dados provavelmente chegarão ao seu componente a partir de adereços ou serão baixados da rede, mas, no nosso caso, gerenciaremos com pouco sangue.

Como o TypeScript é usado neste exemplo, descrevemos algumas entidades:

 type Language = { id: number, name: string, favorite: boolean, } interface AppProps {} interface AppState { data: Array<Language> } 

O tipo de Language nos diz quais campos os itens da lista terão.

Neste exemplo, não obteremos nada dos adereços, portanto a interface AppProps é trivial e, na história, armazenaremos uma matriz de objetos Language , indicada na interface AppState .

Como o código do componente não é muito grande, darei em sua totalidade:

Código do componente do aplicativo
 class App extends Component<AppProps, AppState> { constructor(props: AppProps) { super(props) this.state = { data: fakeData, } } onMoveEnd = ({ data }: OnMoveEndInfo<Language>) => { this.setState({ data: data ? [...data] : [] }) } render() { return ( <View style={styles.root}> <DraggableFlatList data={this.state.data} renderItem={this.renderItem} keyExtractor={(item) => item.id.toString()} scrollPercent={5} onMoveEnd={this.onMoveEnd} /> </View> ) } renderItem = ({ item, move, moveEnd, isActive }: RenderItemInfo<Language>) => { return ( <ListItem name={item.name} move={move} moveEnd={moveEnd} isActive={isActive} /> ) } } 


O método onMoveEnd chamado quando o item é movido. Nesse caso, precisamos colocar a lista com a nova ordem dos elementos no estado, então chamamos o método this.setState .

O método renderItem um item da lista e aceita um objeto do tipo RenderItemInfo <Idioma>. Este objeto inclui os seguintes campos:

  • item - o próximo elemento da matriz passado como dados para a lista,
  • move e moveEnd são funções chamadas quando um item da lista é movido, o componente DraggableFlatList fornece essas funções,
  • isActive é um campo booleano que determina se o item está atualmente arrastável.

O componente para exibir o item da lista é, de fato, TouchableOpacity , que, quando pressionado por um longo tempo, chama de move e, quando liberado, moveEnd - moveEnd .

Código do componente ListItem
 import React from 'react' import { Text, TouchableOpacity } from 'react-native' import styles from './styles' interface ListItemProps { name: string, move: () => void, moveEnd: () => void, isActive: boolean, } const ListItem = ({ name, move, moveEnd, isActive }: ListItemProps) => { return ( <TouchableOpacity style={[styles.root, isActive && styles.active]} onLongPress={move} onPressOut={moveEnd} > <Text style={styles.text}>{name}</Text> </TouchableOpacity> ) } export default ListItem 


Os estilos para todos os componentes são movidos para arquivos separados e não são fornecidos aqui, mas podem ser visualizados no repositório .

O resultado resultante:



Adicione a capacidade de deslizar


Bem, lidamos com sucesso com a primeira parte, seguimos para a segunda parte do balé Marlezon.

Para adicionar a capacidade de deslizar itens da lista, usamos o pacote react-native-swipe-list-view .

Para começar, vamos instalá-lo:

 yarn add react-native-swipe-list-view 

Este pacote contém o componente SwipeRow , que, de acordo com a documentação, deve incluir dois componentes:

 <SwipeRow> <View style={hiddenRowStyle} /> <View style={visibleRowStyle} /> </SwipeRow> 

Observe que a primeira vista é desenhada sob a segunda.

Vamos mudar o código do componente ListItem .

Código do componente ListItem
 import React from 'react' import { Text, TouchableOpacity, View, Image } from 'react-native' import { SwipeRow } from 'react-native-swipe-list-view' import { Language } from '../../App' import styles from './styles' const heart = require('./icons8-heart-24.png') const filledHeart = require('./icons8-heart-24-filled.png') interface ListItemProps { item: Language, move: () => void, moveEnd: () => void, isActive: boolean, onHeartPress: () => void, } const ListItem = ({ item, move, moveEnd, isActive, onHeartPress }: ListItemProps) => { return ( <SwipeRow rightOpenValue={-180}> <View style={styles.hidden}> <TouchableOpacity onPress={onHeartPress}> <Image source={item.favorite ? filledHeart : heart} /> </TouchableOpacity> </View> <TouchableOpacity activeOpacity={1} style={[styles.root, isActive && styles.active]} onLongPress={move} onPressOut={moveEnd} > <Text style={styles.text}>{item.name}</Text> </TouchableOpacity> </SwipeRow> ) } export default ListItem 


Primeiro, adicionamos o componente SwipeRow com a propriedade SwipeRow , que determina a distância pela qual o elemento pode ser deslizado.

Em segundo lugar, movemos nossa TouchableOpacity dentro do SwipeRow e adicionamos uma View, que será desenhada sob esse botão.

Dentro dessa visualização, é desenhada uma imagem para determinar se o idioma é um favorito. Quando você clica nele, o valor deve ser revertido e, como os dados estão no componente pai, é necessário acionar um retorno de chamada aqui que executa esta ação.

Faça as alterações necessárias no componente pai:

Código do componente do aplicativo
 import React, { Component } from 'react' import { View } from 'react-native' import styles from './styles' import DraggableFlatList, { RenderItemInfo, OnMoveEndInfo } from 'react-native-draggable-flatlist' import ListItem from './components/ListItem' import fakeData from './fakeData.json' export type Language = { id: number, name: string, favorite: boolean, } interface AppProps {} interface AppState { data: Array<Language> } class App extends Component<AppProps, AppState> { constructor(props: AppProps) { super(props) this.state = { data: fakeData, } } onMoveEnd = ({ data }: OnMoveEndInfo<Language>) => { this.setState({ data: data ? [...data] : [] }) } toggleFavorite = (value: Language) => { const data = this.state.data.map(item => ( item.id !== value.id ? item : { ...item, favorite: !item.favorite } )) this.setState({ data }) } render() { return ( <View style={styles.root}> <DraggableFlatList data={this.state.data} renderItem={this.renderItem} keyExtractor={(item) => item.id.toString()} scrollPercent={5} onMoveEnd={this.onMoveEnd} /> </View> ) } renderItem = ({ item, move, moveEnd, isActive }: RenderItemInfo<Language>) => { return ( <ListItem item={item} move={move} moveEnd={moveEnd} isActive={isActive} onHeartPress={() => this.toggleFavorite(item)} /> ) } } export default App 


Fontes do projeto no GitHub .

O resultado é apresentado abaixo:

Source: https://habr.com/ru/post/pt460567/


All Articles