提问人:ywin s 提问时间:10/23/2023 最后编辑:ywin s 更新时间:10/23/2023 访问量:34
Flutter:在 ReorderableListView.builder 中从 Bloc State 重新排序列表的问题
Flutter: Issue with Reordering List from Bloc State in ReorderableListView.builder
问:
目前在 Flutter 中使用 BloC 模式时遇到了 ReorderableListView.builder 的问题。
当我尝试通过 ReorderableListView.builder 的 onReorder 回调中的事件更新存储在我的 Bloc State 中的列表时,列表项不会立即直观地重新排序。相反,它会在原来的位置上停留一小会儿,然后跳到重新排序的位置。这给人一种不太愉快的用户体验。
这是我尝试完全依赖 BloC 状态而不使用 localList 的代码:
class ReorderProblem extends StatefulWidget {
final String pageId;
const ReorderProblem({Key? key, required this.pageId}) : super(key: key);
@override
State<ReorderProblem> createState() => _ReorderProblemState();
}
class _ReorderProblemState extends State<ReorderProblem> {
@override
Widget build(BuildContext context) {
// final theme = Theme.of(context);
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
child: BlocBuilder<FormatEditorPageBloc, FormatEditorPageState>(
buildWhen: (previous, current) {
return previous.editingForm.pages[widget.pageId] !=
current.editingForm.pages[widget.pageId];
},
builder: (context, state) {
final page = state.editingForm.pages[widget.pageId];
if (page == null) return const Center(child: Text('Page not found'));
return ReorderableListView.builder(
buildDefaultDragHandles: false,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(page.blocks[index].id),
leading: ReorderableDragStartListener(
index: index,
child: const Icon(Icons.drag_handle),
),
trailing: IconButton(
onPressed: () {
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.addBlockAt(
pageId: page.id,
index: index + 1,
newBlock: null,
),
);
},
icon: const Icon(Icons.add),
),
title: DeltaBlockView(
deltaBlock: page.blocks[index],
onDeltaBlockUpdated: (deltaBlock) {
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.updateBlockAt(
pageId: page.id,
index: index,
updatedBlock: deltaBlock,
),
);
},
)); //const DeltaBlockView());
},
itemCount: page.blocks.length,
onReorder: (oldIndex, newIndex) {
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.reorderBlocks(
pageId: page.id,
oldIndex: oldIndex,
newIndex: newIndex,
),
);
},
);
},
),
);
}
}
为了解决此问题,我创建了一个本地列表 (localList),该列表会立即更新,然后向 Bloc 发送一个事件以更新其状态。这种方法有效,但它使代码看起来杂乱无章。
这是我使用 localList 的代码:
class ReorderProblem extends StatefulWidget {
final String pageId;
const ReorderProblem({Key? key, required this.pageId}) : super(key: key);
@override
State<ReorderProblem> createState() => _ReorderProblemState();
}
class _ReorderProblemState extends State<ReorderProblem> {
@override
Widget build(BuildContext context) {
// final theme = Theme.of(context);
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
child: BlocBuilder<FormatEditorPageBloc, FormatEditorPageState>(
buildWhen: (previous, current) {
return previous.editingForm.pages[widget.pageId] !=
current.editingForm.pages[widget.pageId];
},
builder: (context, state) {
final page = state.editingForm.pages[widget.pageId];
if (page == null) return const Center(child: Text('Page not found'));
return HookBuilder(
builder: (context) {
final localList = useState(page.blocks);
// Listen for external changes to page.blocks and update localList
useEffect(() {
localList.value = page.blocks;
return null; // This represents the cleanup function, which we don't need here.
}, [
page.blocks
]); // This dependency list ensures useEffect runs whenever page.blocks changes.
return ReorderableListView.builder(
buildDefaultDragHandles: false,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(localList.value[index].id),
leading: ReorderableDragStartListener(
index: index,
child: const Icon(Icons.drag_handle),
),
trailing: IconButton(
onPressed: () {
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.addBlockAt(
pageId: page.id,
index: index + 1,
newBlock: null,
),
);
},
icon: const Icon(Icons.add),
),
title: DeltaBlockView(
deltaBlock: localList.value[index],
onDeltaBlockUpdated: (deltaBlock) {
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.updateBlockAt(
pageId: page.id,
index: index,
updatedBlock: deltaBlock,
),
);
},
)); //const DeltaBlockView());
},
itemCount: localList.value.length,
onReorder: (oldIndex, newIndex) {
setState(() {
final blockToMove = localList.value[oldIndex];
if (oldIndex < newIndex) {
localList.value = localList.value
.removeAt(oldIndex)
.insert(newIndex - 1, blockToMove)
.toIList();
} else {
localList.value = localList.value
.removeAt(oldIndex)
.insert(newIndex, blockToMove)
.toIList();
}
context.read<FormatEditorPageBloc>().add(
FormatEditorPageDeltaPageEvent.reorderBlocks(
pageId: page.id,
oldIndex: oldIndex,
newIndex: newIndex,
),
);
});
},
);
},
);
},
),
);
}
}
鉴于上述情况,是否有更优雅的解决方案来解决这个问题,或者这是使用 BloC 模式时推荐的方法?
==== 更新其他信息:
我想为我原来的问题提供一些额外的背景信息。这是我的 Bloc 的片段,我使用 FormatEditorPageDeltaPageEvent 处理重新排序事件:
on<FormatEditorPageDeltaPageEvent>((event, emit) {
event.map(reorderBlocks: (value) {
final blocks = state.editingForm.pages[value.pageId]!.blocks;
final blockToMove = blocks[value.oldIndex];
if (value.oldIndex < value.newIndex) {
emit(state.copyWith.editingForm(
pages: state.editingForm.pages.update(
value.pageId,
(page) => page.copyWith(
blocks: blocks
.removeAt(value.oldIndex)
.insert(value.newIndex - 1, blockToMove)
.toIList(),
),
),
));
} else {
emit(state.copyWith.editingForm(
pages: state.editingForm.pages.update(
value.pageId,
(page) => page.copyWith(
blocks: blocks
.removeAt(value.oldIndex)
.insert(value.newIndex, blockToMove)
.toIList(),
),
),
));
}
},
// other codes
);
});
为了更好地帮助进行故障排除和更全面的视图,我创建了一个最小可重现的代码来复制我所描述的行为。您可以通过此链接在 GitHub 上访问它。 如果您在 Web 上运行提供的最小可重现代码,您会注意到,当您尝试更改项目的顺序时,它最初会恢复到其原始位置,然后迅速捕捉到其新的预期位置。
任何见解或建议将不胜感激!
答:
我注意到你正在使用buildWhen
buildWhen: (previous, current) {
return previous.editingForm.pages[widget.pageId] !=
current.editingForm.pages[widget.pageId];
},
你确定你每次都是不同的对象,你收到状态吗?editinForm.pages
你是否知道它们需要是不同的对象,大多数时候,当我们发出新的状态来使其不同时,我们确实这样做了
emit(state.copyWith(....))
评论