React Native:如何实现2列刷卡?
React Native: How to implement 2 columns of swipping cards?
我正在尝试在 2 列中实现可滚动的卡片列表。卡片应可向左或向右滑出屏幕以移除。
基本上,它应该像 Chrome 应用程序当前显示的选项卡列表一样,可以滑动关闭。请参阅示例图片 here。
我能够使用 FlatList 在 2 列中实现卡片列表。但是,我无法让卡片可以刷卡。我试过 react-tinder-card 但它不能限制上下滑动,因此列表变得不可滚动。 react-native-deck-swiper 也不适用于列表。
感谢任何帮助。谢谢!
我要实现一个满足以下要求的组件:
- 创建两列
FlatList
,其中的项目是您的卡片。
- 实施识别
swipeLeft
和 swipeRight
操作的手势处理,这些操作将移除刷过的卡。
swipe
动作应该是动画的,这意味着我们有某种 drag of the screen
行为。
我将使用基本的 react-native FlatList
和 numColumns={2}
和 react-native-swipe-list-view 来处理 swipeLeft
和 swipeRight
操作以及想要的动画。
我将执行一个 fire and forget
操作,因此删除一个项目后,它就永远消失了。如果我们希望能够恢复已删除的项目,我们将在稍后实施 restore mechanism
。
我的初始实现如下:
- 使用
numColumns={2}
创建一个 FlatList
和一些额外的虚拟样式以增加一些边距。
- 使用
useState
创建状态,其中包含代表我们卡片的对象数组。
- 实现一个函数,从提供了 id 的状态中删除一个项目。
- 将要呈现的项目包装在
SwipeRow
中。
- 将
removeItem
函数传递给 swipeGestureEnded
属性。
import React, { useState } from "react"
import { FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow swipeGestureEnded={() => removeItem(item.id)}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
</SafeAreaView>
)
}
请注意,我们的对象中需要某种 属性 以确定要删除的对象。我在这里使用了基本的 id
属性,这在使用 FlatList
时很常见。如果您从 API
中检索数据,而 API
不提供相同的 id
,那么我们可以先进行一些预处理 (normalization
),然后自己添加 id
属性.
初始视图如下所示。
滑动,假设向右或向左 'Title 6' 的项目将其删除。
可能还需要实现以下功能。
- 如果该项目位于第一列,则只需向左滑动即可删除该项目。
- 如果项目在第二列,则只有向右滑动才能删除项目。
这很容易实现,使用传递给 renderItem
函数的 index
参数和传递给 swipeGestureEnded
的 gestureState
的 vx
属性] 函数。
这是完整的工作实施。
import React, { useState } from "react"
import { FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow
swipeGestureEnded={(key, event) => {
if (event.gestureState.vx < 0) {
if (index % 2 === 0) {
removeItem(item.id)
}
} else if (event.gestureState.vx >= 0) {
if (index % 2 === 1) {
removeItem(item.id)
}
}
}}
disableLeftSwipe={index % 2 === 1}
disableRightSwipe={index % 2 === 0}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
</SafeAreaView>
)
}
由于基于 FlatList
的索引为零,当且仅当 index % 2 === 1
时(例如,索引为 3
的项目总是在第二列中)第二列,因此不能被 2
整除),另一方面,当且仅当 index % 2 === 0
即 index
可以被 2
整除时,项目才会出现在第一列中。
SwipeRowComponent
中有几个回调函数道具应该在某些情况下触发。但是,它们中的大多数在我的设置中不起作用,我仍然不知道为什么。我通过使用 event.gestureState.vx
属性 使它工作,如果我们向左滑动则为负,如果我们向右滑动则为正(包括零)。
可能需要实现一个 undo
按钮,因为它在此类功能中很常见。这可以按如下方式完成:
- 实现第二个状态,表示
Queue
保存最后删除的项目。 undo
按钮然后只是 pops
最后删除的项目。
这是一个完全可用的实现,它带有一个虚拟 undo
按钮,可以实现这一点。
import React, { useState } from "react"
import { Button, FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
function undoRemove() {
if (removed && removed.length > 0) {
let itemToUndo = removed[removed.length - 1]
setCards([...cards, itemToUndo])
setRemoved(removed.filter((c) => c.id !== itemToUndo.id))
}
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow
swipeGestureEnded={(key, event) => {
if (event.gestureState.vx < 0) {
if (index % 2 === 0) {
removeItem(item.id)
}
} else if (event.gestureState.vx >= 0) {
if (index % 2 === 1) {
removeItem(item.id)
}
}
}}
disableLeftSwipe={index % 2 === 1}
disableRightSwipe={index % 2 === 0}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
<Button onPress={undoRemove} title="Undo" />
</SafeAreaView>
)
}
请注意,我的 undo
按钮只是将删除的项目附加到列表的末尾。如果你想保留初始索引,那么你需要保存旧索引并将项目推到正确的位置。
这是我上次实施的工作 snack。
我正在尝试在 2 列中实现可滚动的卡片列表。卡片应可向左或向右滑出屏幕以移除。
基本上,它应该像 Chrome 应用程序当前显示的选项卡列表一样,可以滑动关闭。请参阅示例图片 here。
我能够使用 FlatList 在 2 列中实现卡片列表。但是,我无法让卡片可以刷卡。我试过 react-tinder-card 但它不能限制上下滑动,因此列表变得不可滚动。 react-native-deck-swiper 也不适用于列表。
感谢任何帮助。谢谢!
我要实现一个满足以下要求的组件:
- 创建两列
FlatList
,其中的项目是您的卡片。 - 实施识别
swipeLeft
和swipeRight
操作的手势处理,这些操作将移除刷过的卡。 swipe
动作应该是动画的,这意味着我们有某种drag of the screen
行为。
我将使用基本的 react-native FlatList
和 numColumns={2}
和 react-native-swipe-list-view 来处理 swipeLeft
和 swipeRight
操作以及想要的动画。
我将执行一个 fire and forget
操作,因此删除一个项目后,它就永远消失了。如果我们希望能够恢复已删除的项目,我们将在稍后实施 restore mechanism
。
我的初始实现如下:
- 使用
numColumns={2}
创建一个FlatList
和一些额外的虚拟样式以增加一些边距。 - 使用
useState
创建状态,其中包含代表我们卡片的对象数组。 - 实现一个函数,从提供了 id 的状态中删除一个项目。
- 将要呈现的项目包装在
SwipeRow
中。 - 将
removeItem
函数传递给swipeGestureEnded
属性。
import React, { useState } from "react"
import { FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow swipeGestureEnded={() => removeItem(item.id)}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
</SafeAreaView>
)
}
请注意,我们的对象中需要某种 属性 以确定要删除的对象。我在这里使用了基本的 id
属性,这在使用 FlatList
时很常见。如果您从 API
中检索数据,而 API
不提供相同的 id
,那么我们可以先进行一些预处理 (normalization
),然后自己添加 id
属性.
初始视图如下所示。
滑动,假设向右或向左 'Title 6' 的项目将其删除。
可能还需要实现以下功能。
- 如果该项目位于第一列,则只需向左滑动即可删除该项目。
- 如果项目在第二列,则只有向右滑动才能删除项目。
这很容易实现,使用传递给 renderItem
函数的 index
参数和传递给 swipeGestureEnded
的 gestureState
的 vx
属性] 函数。
这是完整的工作实施。
import React, { useState } from "react"
import { FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow
swipeGestureEnded={(key, event) => {
if (event.gestureState.vx < 0) {
if (index % 2 === 0) {
removeItem(item.id)
}
} else if (event.gestureState.vx >= 0) {
if (index % 2 === 1) {
removeItem(item.id)
}
}
}}
disableLeftSwipe={index % 2 === 1}
disableRightSwipe={index % 2 === 0}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
</SafeAreaView>
)
}
由于基于 FlatList
的索引为零,当且仅当 index % 2 === 1
时(例如,索引为 3
的项目总是在第二列中)第二列,因此不能被 2
整除),另一方面,当且仅当 index % 2 === 0
即 index
可以被 2
整除时,项目才会出现在第一列中。
SwipeRowComponent
中有几个回调函数道具应该在某些情况下触发。但是,它们中的大多数在我的设置中不起作用,我仍然不知道为什么。我通过使用 event.gestureState.vx
属性 使它工作,如果我们向左滑动则为负,如果我们向右滑动则为正(包括零)。
可能需要实现一个 undo
按钮,因为它在此类功能中很常见。这可以按如下方式完成:
- 实现第二个状态,表示
Queue
保存最后删除的项目。undo
按钮然后只是pops
最后删除的项目。
这是一个完全可用的实现,它带有一个虚拟 undo
按钮,可以实现这一点。
import React, { useState } from "react"
import { Button, FlatList, SafeAreaView, Text, View } from "react-native"
import { SwipeRow } from "react-native-swipe-list-view"
const data = [
{
id: "0",
title: "Title 1",
},
{
id: "1",
title: "Title 2",
},
{
id: "2",
title: "Title 3",
},
{
id: "3",
title: "Title 4",
},
{
id: "4",
title: "Title 5",
},
{
id: "5",
title: "Title 6",
},
{
id: "6",
title: "Title 7",
},
{
id: "7",
title: "Title 8",
},
]
export function Test() {
const [cards, setCards] = useState(data)
const [removed, setRemoved] = useState([])
function removeItem(id) {
let previous = [...cards]
let itemToRemove = previous.find((x) => x.id === id)
setCards(previous.filter((c) => c.id !== id))
setRemoved([...removed, itemToRemove])
}
function undoRemove() {
if (removed && removed.length > 0) {
let itemToUndo = removed[removed.length - 1]
setCards([...cards, itemToUndo])
setRemoved(removed.filter((c) => c.id !== itemToUndo.id))
}
}
return (
<SafeAreaView style={{ margin: 20 }}>
<FlatList
data={cards}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ index, item }) => (
<SwipeRow
swipeGestureEnded={(key, event) => {
if (event.gestureState.vx < 0) {
if (index % 2 === 0) {
removeItem(item.id)
}
} else if (event.gestureState.vx >= 0) {
if (index % 2 === 1) {
removeItem(item.id)
}
}
}}
disableLeftSwipe={index % 2 === 1}
disableRightSwipe={index % 2 === 0}>
<View />
<View style={{ margin: 20, borderWidth: 1, padding: 20 }}>
<Text>{item.title}</Text>
</View>
</SwipeRow>
)}
/>
<Button onPress={undoRemove} title="Undo" />
</SafeAreaView>
)
}
请注意,我的 undo
按钮只是将删除的项目附加到列表的末尾。如果你想保留初始索引,那么你需要保存旧索引并将项目推到正确的位置。
这是我上次实施的工作 snack。