场景: 我有一个 TextInput,您可以在其中输入一些名称。每个名称都存储在一个数组中,我想在 ScrollView 中呈现它们。用户应该能够通过拖放来重新排列 ScrollView 中的名称。
我的代码基于这个很棒的 Expo Snack,它展示了带有拖放功能的 ScrollView。小吃是这个YouTube教程的更新(和工作)版本。
它的工作方式是有一个 Reanimated Shared Value,它使用一个对象将每个数组项的键映射到它的当前位置。这个对象的类型是
{ [id: string]: number }
。对于我的名字数组(请假设每个名字都是唯一的)这可能是这样的:
{
"Thomas": 2,
"Stefan": 0,
"Alex": 1,
}
共享值
positions
在SortableList.tsx
中用这样的对象初始化并传递给每个列表项。当前移动的列表项更新共享值。如果有人可以告诉我如何在不破坏 ScrollView 的情况下在 Snack 中动态添加新歌曲,那将是完全足够的:-)
问题: 当我重新渲染 SortableList 并像这样将一个新对象传递给 useSharedValue 时
const positions = useSharedValue({"Thomas": 2,"Stefan": 0,"Alex": 1,"NewName": 4});
hook stills 返回旧值。我确实设置了断点并检查了所有内容:重新呈现 SortableList,重新呈现每个子项,新名称已成功添加到数组并呈现为列表的子项。只有 useSharedValue 仍然返回陈旧的 postitions 对象。
预期行为: 当我的组件重新渲染并且我将一个新对象传递给 useSharedValue 时,它应该返回一个基于新对象的新共享值。
代码:
export default function TextInputAndList() {
const [text, setText] = useState("");
const textInputRef = useRef<TextInput>(null);
// dummyData for initially rendering a scrollable list. Would be empty once everything works.
const [names, setNames] = useState<string[]>(dummyData);
function addName() {
if (textInputRef.current) {
textInputRef.current.clear();
setNames(prev => [...prev, text]);
}
}
return (
<View>
<TextInput
ref={textInputRef}
placeholder="Enter name"
onChangeText={newText => setText(newText)}
onSubmitEditing={addName}
defaultValue={text}
/>
<SortableList listItems={names} />
</View>
);
}
function SortableList({ listItems }: { listItems: string[] }) {
// I'm aware that this reverts my rearrangment. Please see my comment below.
const positions = useSharedValue(listToObject(listItems)); // The troublemaker
const scrollY = useSharedValue(0);
const autoScroll = useSharedValue(ScrollDirection.None);
const scrollViewRef = useAnimatedRef<Animated.ScrollView>();
const dimensions = useWindowDimensions();
const insets = useSafeAreaInsets();
const [contentHeight, setContentHeight] = useState(listItems.length * HEIGHT);
useAnimatedReaction(
() => scrollY.value,
scrolling => {
scrollTo(scrollViewRef, 0, scrolling, false);
}
);
const handleScroll = useAnimatedScrollHandler(event => {
scrollY.value = event.contentOffset.y;
});
const containerHeight = dimensions.height - insets.top - insets.bottom;
return (
<>
<View style={{ flex: 1 }}>
<Animated.ScrollView
ref={scrollViewRef}
onScroll={handleScroll}
scrollEventThrottle={16}
style={{
flex: 1,
position: "relative",
}}
contentContainerStyle={{
height: contentHeight,
}}
>
{listItems.map(listItem => (
<MovableObject
key={listItem}
id={listItem}
positions={positions}
lowerBound={scrollY}
autoScrollDirection={autoScroll}
itemsCount={listItems.length}
containerHeight={containerHeight}
/>
))}
</Animated.ScrollView>
</View>
</>
);
}
// memo() to avoid rerendering whenever the TextInput changes
export default React.memo(SortableList);
MovableObject请参考截图中的MovableSong
到目前为止我尝试了什么:
我知道如果 useSharedValue 实际上会重新初始化,我发布的代码会恢复所有拖放重新排列。
但是,我已经尝试将
listToObject(listItems)
存储在一个状态中并将其作为 props 传递,并将其存储在 redux 状态中。我仍然遇到同样的问题,所以这就是为什么我要尽可能简单地在这里编写代码。TLDR: 我想要一个可滚动的列表,我可以在其中通过拖放重新排列项目并动态添加新项目。一个现有的例子是 Spotify 的播放列表编辑器。但不是删除列表项的“减号”按钮我想要(一个 TextInput)添加新的。