You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中ReorderableListView子项显示切换异常求助

问题解决:ReorderableListView拖拽时统一显示标题,完成后恢复详情

核心问题分析

  1. 不稳定的Key:使用Random().nextInt生成Key会导致ReorderableListView无法正确跟踪列表项,引发状态显示异常。
  2. 单一状态控制:用全局isLongPress布尔值控制所有项的显示,无法精准对应拖拽状态。
  3. 未关联拖拽行为:通过点击切换状态,而非绑定拖拽的开始/结束事件,不符合"拖拽时显示标题"的需求。

修复方案

1. 控制器添加拖拽状态变量

在你的GetX控制器中添加响应式变量,跟踪拖拽是否进行:

class YourController extends GetxController {
  // ... 其他现有变量
  RxBool isReordering = false.obs;

  void reorderWidgets(int oldIndex, int newIndex) {
    if (newIndex > oldIndex) newIndex -= 1;
    // 执行列表重排序逻辑
    final item = exerciseList.removeAt(oldIndex);
    exerciseList.insert(newIndex, item);
    // 同步更新types、sets等关联列表
    final type = types.removeAt(oldIndex);
    types.insert(newIndex, type);
    final setItem = sets.removeAt(oldIndex);
    sets.insert(newIndex, setItem);
    // 重排序完成后关闭拖拽状态
    isReordering.value = false;
  }
}

2. 重构ReorderableListView代码

替换不稳定Key,绑定拖拽事件控制状态,实现条件显示:

ReorderableListView(
  physics: const NeverScrollableScrollPhysics(),
  shrinkWrap: true,
  onReorder: (int oldIndex, int newIndex) {
    controller.reorderWidgets(oldIndex, newIndex);
  },
  children: List.generate(controller.exerciseList.length, (index) {
    final exercise = controller.exerciseList[index];
    return GestureDetector(
      // 使用稳定的Key,建议用exercise的唯一ID,无ID则用index
      key: ValueKey(exercise.exerciseName ?? index),
      // 长按开始时开启拖拽状态
      onLongPressStart: (_) => controller.isReordering.value = true,
      // 长按结束(拖拽取消)时关闭拖拽状态
      onLongPressEnd: (_) => Future.delayed(const Duration(milliseconds: 100), () {
        controller.isReordering.value = false;
      }),
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 15),
        margin: const EdgeInsets.symmetric(vertical: 7),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(10),
          border: Border.all(
            color: ColorRes.yellowDark.withOpacity(1),
            width: 0.5,
          ),
        ),
        // 根据拖拽状态切换显示内容
        child: Obx(() => controller.isReordering.value 
            ? _buildTitleOnly(exercise, index) 
            : _buildFullContent(exercise, index)),
      ),
    );
  }),
)

3. 提取复用组件

将标题和完整内容拆分为独立组件,减少代码重复:

// 仅显示标题的组件
Widget _buildTitleOnly(Exercise exercise, int index) {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 15),
    child: Row(
      children: [
        Container(
          height: Get.height * 0.06,
          width: Get.height * 0.06,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            border: Border.all(
              color: ColorRes.yellowDark.withOpacity(1),
              width: 1,
            ),
          ),
          clipBehavior: Clip.hardEdge,
          child: ClipRRect(
            borderRadius: BorderRadius.circular(9),
            child: CachedNetworkImage(
              imageUrl: exercise.exerciseImage ?? "",
              fit: BoxFit.fill,
              placeholder: (context, url) => Image.asset(AssetRes.galleryIcon, scale: 8),
              errorWidget: (context, url, error) => Image.asset(AssetRes.galleryIcon, scale: 8),
            ),
          ),
        ),
        SizedBox(width: Get.width * 0.04),
        SizedBox(
          width: Get.width * 0.6,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                (exercise.exerciseName ?? "").toUpperCase(),
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: interMedium(
                  fontSize: 15,
                  fontWeight: FontWeight.w500,
                  color: ColorRes.color111111,
                ),
              ),
              const SizedBox(height: 3),
              Container(
                height: 1,
                width: 50,
                color: ColorRes.yellowDark.withOpacity(1),
              ),
            ],
          ),
        ),
        const Spacer(),
        InkWell(
          onTap: () {
            int count = 0;
            for (var e in (exercise.setsList ?? [])) {
              if (e["type"] == "set") count++;
            }
            controller.setsCount -= count;
            exercise.setsList?.clear();
            controller.exerciseList.removeAt(index);
            controller.types.removeAt(index);
            controller.sets.removeAt(index);
            editWorkOutController.update(["edit_workout"]);
          },
          child: Image.asset(AssetRes.deleteIcon, scale: 2),
        ),
      ],
    ),
  );
}

// 显示完整内容的组件(复用标题组件)
Widget _buildFullContent(Exercise exercise, int index) {
  return Column(
    children: [
      _buildTitleOnly(exercise, index),
      SizedBox(height: Get.height * 0.02),
      SizedBox(
        width: Get.width - 30,
        child: Padding(
          padding: const EdgeInsets.only(left: 30).copyWith(right: 40),
          child: Text(exercise.details ?? ""), // 替换为你的详情内容
        ),
      ),
    ],
  );
}

修复效果

  • 拖拽开始时,所有列表项自动切换为仅显示标题
  • 拖拽完成或取消后,立即恢复显示标题+详情
  • 列表项在重排序过程中状态跟踪准确,不会出现显示错乱

内容的提问来源于stack exchange,提问作者Mobile Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 12:33:15