React Native: crea una lista arrastrable y deslizable

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:

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


All Articles