Hoy es difícil sorprender a cualquiera con la capacidad de deslizar elementos de la lista en aplicaciones móviles. Una de nuestras aplicaciones de reacción nativa también tenía dicha funcionalidad, pero recientemente había una necesidad de expandirla con la capacidad de arrastrar y soltar elementos de la lista. Y dado que el proceso de encontrar una solución me costó una cierta cantidad de células nerviosas, decidí presentar un breve artículo para ahorrar tiempo valioso para las generaciones futuras.

En nuestra aplicación, utilizamos el paquete
react-native-swipe-list-view
para crear la lista deslizable. El primer pensamiento fue tomar un paquete con la funcionalidad de arrastrar y soltar y cruzar el erizo con una serpiente.
Una búsqueda en Internet dio tres candidatos:
react-native-draggable-list
,
react-native-sortable-list
y
react-native-draggable-flatlist
.
Usando el primer paquete, no fue posible ejecutar incluso el ejemplo adjunto (sin embargo, no solo para mí, el problema correspondiente se indica en los
problemas ).
Tuve que jugar con el segundo paquete, pero logré crear una lista arrastrable y deslizable. Sin embargo, el resultado no fue inspirado: el componente parpadeaba descaradamente: parpadeaba el rediseño, la caída de elementos mucho más allá de la lista, o incluso su desaparición. Quedó claro que de esta forma no se puede usar.
Al principio, el último paquete también se comportó como una mujer caprichosa, pero luego resultó que simplemente no sabía cómo cocinarlo. Habiendo recogido una llave del corazón de esta "dama", logré lograr un resultado aceptable.
En nuestro proyecto, había una lista deslizable a la que tenía que sujetar arrastrar y soltar, pero en la práctica es mejor comenzar desde el otro extremo: primero haga una lista arrastrable y luego agregue la capacidad de deslizar.
Se supone que los lectores deben saber cómo crear un proyecto nativo de reacción, así que concentrémonos en crear la lista que necesitamos. El ejemplo que se analiza a continuación es el código TypeScript.
Hacer una lista arrastrable
Entonces, comencemos instalando el paquete:
yarn add react-native-draggable-flatlist
Importamos los módulos necesarios:
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'
Aquí
DraggableFlatList
es un componente del paquete instalado que implementa la función de arrastrar y soltar,
ListItem
es nuestro componente para mostrar un elemento de lista (el código se presentará a continuación),
fakeData
es un archivo json que contiene datos falsos, en este caso, una matriz de objetos de la forma:
{"id": 0, "name": "JavaScript", "favorite": false}
En una aplicación real, estos datos probablemente llegarán a su componente desde accesorios o se descargarán de la red, pero en nuestro caso lo manejaremos con poca sangre.
Dado que TypeScript se usa en este ejemplo, describimos algunas entidades:
type Language = { id: number, name: string, favorite: boolean, } interface AppProps {} interface AppState { data: Array<Language> }
El tipo de
Language
nos dice qué campos tendrán los elementos de la lista.
En este ejemplo, no obtendremos nada de los accesorios, por lo que la interfaz de
AppProps
es trivial, y en la historia almacenaremos una matriz de objetos de
Language
, que se indica en la interfaz de
AppState
.
Como el código del componente no es muy grande, lo daré en su totalidad:
Código de componente de la aplicación 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} /> ) } }
Se
onMoveEnd
método
onMoveEnd
cuando se mueve el elemento. En este caso, necesitamos poner la lista con el nuevo orden de elementos en el estado, por lo que llamamos al método
this.setState
.
El método
renderItem
un elemento de lista y acepta un objeto de tipo RenderItemInfo <Language>. Este objeto incluye los siguientes campos:
item
: el siguiente elemento de la matriz pasado como datos a la lista,move
y moveEnd
son funciones que se moveEnd
cuando se mueve un elemento de la lista, el componente DraggableFlatList
proporciona estas funciones,isActive
es un campo booleano que determina si el elemento se puede arrastrar actualmente.
El componente para mostrar el elemento de la lista es, de hecho,
TouchableOpacity
, que, cuando se presiona durante mucho tiempo, llama a
move
, y cuando se suelta, se
moveEnd
.
Código de 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
Los estilos para todos los componentes se mueven a archivos separados y no se proporcionan aquí, pero se pueden ver en el
repositorio .
El resultado resultante:

Agregue la capacidad de deslizar
Bueno, superamos con éxito la primera parte, pasamos a la segunda parte del ballet Marlezon.
Para agregar la capacidad de deslizar elementos de la lista, utilizamos el paquete
react-native-swipe-list-view
.
Para comenzar, instálelo:
yarn add react-native-swipe-list-view
Este paquete contiene el componente
SwipeRow
, que, según la documentación, debe incluir dos componentes:
<SwipeRow> <View style={hiddenRowStyle} /> <View style={visibleRowStyle} /> </SwipeRow>
Tenga en cuenta que la primera Vista se dibuja debajo de la segunda.
Cambiemos el código del componente
ListItem
.
Código de 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
Primero, agregamos el componente
SwipeRow
con la propiedad
SwipeRow
, que determina la distancia sobre la cual se puede deslizar el elemento.
En segundo lugar, movimos nuestra
TouchableOpacity
dentro de
SwipeRow
y agregamos una Vista, que se dibujará debajo de este botón.
Dentro de esta vista, se dibuja una imagen para determinar si el idioma es el favorito. Cuando hace clic en él, el valor debe invertirse, y dado que los datos están en el componente principal, debe lanzar una devolución de llamada aquí que realice esta acción.
Realice los cambios necesarios en el componente principal:
Código de componente de la aplicación 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
Fuentes del proyecto en
GitHub .
El resultado se presenta a continuación:
