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

Flutter长按ListView项时FloatingActionButton不显示问题求助

Flutter长按ListView项显示删除浮动按钮失败问题

我尝试在长按ListView列表项时显示一个文本为“Usuń(删除)”的FloatingActionButton,但始终无法显示。界面上已有一个正常显示的“Dodaj(添加)”按钮,先后两种实现方式都失败了:

  • 最初在GestureDetector的onLongPress回调中直接创建FloatingActionButton,无任何效果;
  • 后来用布尔状态变量showButton控制显示,在长按回调中调用setState修改状态,但按钮还是不显示,连回调里的print语句都没输出。

初始代码片段:

return GestureDetector(
          onLongPress: () {
            print('test');
            FloatingActionButton.extended(
              onPressed: () {
                print('test1');
              },
              icon: const Icon(Icons.add),
              label: const Text('Usuń'),
            );

UPDATE 1

为方便理解构建流程,补充更多代码。“Dodaj”按钮工作正常,现在要实现长按列表项显示“Usuń”按钮,帮忙指出问题:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          //addItem();
        },
        icon: const Icon(Icons.add),
        label: const Text('Dodaj'),
      ),
      appBar: AppBar(
        backgroundColor: Colors.grey[900],
        title: const Text('Lista zakupowa'),
        actions: [
          IconButton(
            onPressed: signUserOut,
            icon: const Icon(Icons.logout),
          )
        ],
      ),
      body: Center(
        child: FutureBuilder<List<PostListDetails>>(
          future: postsFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),
      ),
    );
  }


  // 用于在屏幕上显示获取到的数据
  Widget buildPosts(List<PostListDetails> posts) {
    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        int customCompare(PostListDetails a, PostListDetails b) {
          if (a.status != b.status) {
            return a.status!.toInt() - b.status!.toInt();
          }
          final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
          if (categoryCompare != 0) {
            return categoryCompare;
          }
          return a.nazwa.toString().compareTo(b.nazwa.toString());
        }
        return GestureDetector(
          onLongPress: () {
            print('test1');
            FloatingActionButton.extended(
              onPressed: () {
                print('test2');
              },
              icon: const Icon(Icons.add),
              label: const Text('Usuń'),
            );
          },
          child: Container(
            color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
            margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
            height: 50,
            width: double.maxFinite,
            child: Row(
              children: [
                Expanded(
                  flex: 8,
                    child: Column(
                    ...
                    ),
                  ),
                   Expanded(
                    flex: 2,
                    child: Column(
                    ...
                    ),
                  ),
            ],
            ),
            ),
            );
          },
        );
    }

UPDATE 2

修改代码后问题仍未解决,长按列表项后“Usuń”按钮不显示,test1和test2都没打印。当前代码如下:

bool showButton = false;

@override
    Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          //addList();
        },
        icon: const Icon(Icons.add),
        label: const Text('Dodaj'),
      ),
      appBar: AppBar(
        backgroundColor: Colors.grey[900],
        title: const Text('Lista zakupowa'),
        actions: [
          IconButton(
            onPressed: signUserOut,
            icon: const Icon(Icons.logout),
          )
        ],
      ),
      body: Center(
        child: FutureBuilder<List<PostListDetails>>(
          future: postsFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),
      ),
    );
  }


  // 用于在屏幕上显示获取到的数据
  Widget buildPosts(List<PostListDetails> posts) {
    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        int customCompare(PostListDetails a, PostListDetails b) {
          if (a.status != b.status) {
            return a.status!.toInt() - b.status!.toInt();
          }
          final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
          if (categoryCompare != 0) {
            return categoryCompare;
          }
          return a.nazwa.toString().compareTo(b.nazwa.toString());
        }
        GestureDetector(
          onLongPress: () {
            print('test1');
            setState(() => showButton = true);
          },
        );
        showButton ? FloatingActionButton.extended(
          onPressed: () {
            print('test2');
            //removeList();
          },
          icon: const Icon(Icons.add),
          label: const Text('Usuń'),
        ) : null;
        return Container(
          color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
          margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
          padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
          height: 50,
          width: double.maxFinite,
          child: Row(
            children: [
              Expanded(
                flex: 8,
                child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(post.nazwa!, textAlign: TextAlign.left, style: const TextStyle(fontWeight: FontWeight.bold,fontSize: 15,color: Colors.black)),
                      Text('${post.waga!.toString()} g.', textAlign: TextAlign.left, style: const TextStyle(fontWeight: FontWeight.normal,fontSize: 15,color: Colors.grey))
                    ]
                    ),
                  ),
                   Expanded(
                    flex: 2,
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Expanded(
                            flex: 0,
                            child: Row(
                            ),
                          ),
                          Expanded(
                            flex: 10,
                            child: Row(
                              children: [
                                CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
                                Checkbox(
                                  value: Int2Bool(post.status!.toInt()),
                                  onChanged: (value) {
                                    setState(() {post.status = Bool2Int(value!);});
                                    saveStatus(post.nazwa!, post.status!, index);
                                    posts.sort(customCompare);
                                  },
                                ),
                              ]
                            ),
                          )
                      ]
                    ),
                  ),
            ],
            ),
          );
      },
    );
  } 

问题分析与修复方案

核心问题点

  1. GestureDetector未绑定到列表项:UPDATE2中创建了GestureDetector但未将其作为列表项的父Widget,导致长按事件完全无法触发,回调代码根本不会执行。
  2. 浮动按钮布局逻辑错误:试图在ListView的itemBuilder中创建FloatingActionButton,不符合Scaffold的布局规则——浮动按钮必须作为Scaffold的floatingActionButton属性存在,不能嵌套在列表项内。
  3. 状态控制未关联到正确的布局节点:即使修改了showButton状态,也没有将删除按钮挂载到Scaffold的浮动按钮位置,自然无法显示。

修复后的完整代码示例

bool showDeleteButton = false;
// 记录长按选中的列表项标识,用于后续删除操作
String? selectedItemId;

@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.grey[100],
    floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    // 使用Stack同时管理两个浮动按钮的显示
    floatingActionButton: Stack(
      fit: StackFit.expand,
      children: [
        // 始终显示的添加按钮
        Positioned(
          bottom: 16,
          left: 0,
          right: 0,
          child: FloatingActionButton.extended(
            onPressed: () {
              //addList();
            },
            icon: const Icon(Icons.add),
            label: const Text('Dodaj'),
          ),
        ),
        // 长按后显示的删除按钮,可调整位置
        if (showDeleteButton)
          Positioned(
            bottom: 80,
            left: 0,
            right: 0,
            child: FloatingActionButton.extended(
              onPressed: () {
                print('test2');
                //removeList(selectedItemId!);
                // 删除完成后隐藏按钮
                setState(() => showDeleteButton = false);
              },
              icon: const Icon(Icons.delete),
              label: const Text('Usuń'),
              backgroundColor: Colors.red,
            ),
          ),
      ],
    ),
    appBar: AppBar(
      backgroundColor: Colors.grey[900],
      title: const Text('Lista zakupowa'),
      actions: [
        IconButton(
          onPressed: signUserOut,
          icon: const Icon(Icons.logout),
        )
      ],
    ),
    body: Center(
      child: FutureBuilder<List<PostListDetails>>(
        future: postsFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const CircularProgressIndicator();
          } else if (snapshot.hasData) {
            return buildPosts(snapshot.data!);
          } else {
            return const Text("Brak danych...");
          }
        },
      ),
    ),
  );
}

Widget buildPosts(List<PostListDetails> posts) {
  // 将排序逻辑移到itemBuilder外,避免重复创建比较函数
  posts.sort((a, b) {
    if (a.status != b.status) {
      return a.status!.toInt() - b.status!.toInt();
    }
    final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
    if (categoryCompare != 0) {
      return categoryCompare;
    }
    return a.nazwa.toString().compareTo(b.nazwa.toString());
  });

  return ListView.builder(
    itemCount: posts.length,
    itemBuilder: (context, index) {
      final post = posts[index];
      // 正确将GestureDetector作为列表项的父Widget,绑定长按事件
      return GestureDetector(
        onLongPress: () {
          print('test1');
          setState(() {
            showDeleteButton = true;
            selectedItemId = post.nazwa; // 假设nazwa是列表项的唯一标识
          });
        },
        // 可选:点击列表项隐藏删除按钮,优化交互
        onTap: () {
          if (showDeleteButton) {
            setState(() => showDeleteButton = false);
          }
        },
        child: Container(
          color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
          margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
          padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
          height: 50,
          width: double.maxFinite,
          child: Row(
            children: [
              Expanded(
                flex: 8,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(post.nazwa!, textAlign: TextAlign.left, style: const TextStyle(fontWeight: FontWeight.bold,fontSize: 15,color: Colors.black)),
                    Text('${post.waga!.toString()} g.', textAlign: TextAlign.left, style: const TextStyle(fontWeight: FontWeight.normal,fontSize: 15,color: Colors.grey))
                  ]
                ),
              ),
              Expanded(
                flex: 2,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Expanded(
                      flex: 0,
                      child: Row(),
                    ),
                    Expanded(
                      flex: 10,
                      child: Row(
                        children: [
                          CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
                          Checkbox(
                            value: Int2Bool(post.status!.toInt()),
                            onChanged: (value) {
                              setState(() {post.status = Bool2Int(value!);});
                              saveStatus(post.nazwa!, post.status!, index);
                            },
                          ),
                        ]
                      ),
                    )
                  ]
                ),
              ),
            ],
          ),
        ),
      );
    },
  );
}

关键修复说明

  • 把GestureDetector作为列表项的直接父Widget,确保长按事件能被正确触发;
  • 在Scaffold的floatingActionButton中使用Stack组件,同时管理添加按钮和删除按钮的显示位置与状态;
  • 将排序逻辑从itemBuilder中提取出来,避免每次构建列表项时重复创建比较函数,提升性能;
  • 添加点击列表项隐藏删除按钮的逻辑,优化用户交互体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:02:02