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

Flutter:如何单独控制列表中单个Widget的可见性?

问题描述

从Firebase读取数据生成Widget列表,点击某一项时希望仅切换该Widget的内容可见性,但当前用Visibility绑定全局的valueVisible变量,导致所有项的可见性同步变化,如何实现单独控制?

用户当前代码:

body: StreamBuilder<QuerySnapshot>(
        stream: _keyService.getKeys(),
        builder: (context, snapshot) {
          return !snapshot.hasData
              ? const Center(child: CircularProgressIndicator())
              : ListView.builder(
                  itemCount: snapshot.data!.docs.length,
                  itemBuilder: (context, index) {
                    DocumentSnapshot mypost = snapshot.data!.docs[index];

                    return Padding(
                      padding: EdgeInsets.all(size * 0.3),
                      child: InkWell(
                        onTap: () {
                          valueVisible = !valueVisible;
                        },
                  
                        child: Container(
                          decoration: BoxDecoration(
                              color: ColorItems.mainColor,
                              border: Border.all(width: 5, color: Colors.grey),
                              borderRadius: BorderRadius.all(Radius.circular(20))),
                          child: Padding(
                            padding: EdgeInsets.all(size),
                            child: Container(
                              child: Row(
                                children: [
                                  Expanded(
                                    child: Text(
                                      "${mypost['key']}",
                                      style: const TextStyle(
                                          color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                    ),
                                  ),
                                  const Text(
                                    ": ",
                                    style:
                                        TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                  ),
                                  const SizedBox(
                                    width: 20,
                                  ),
                                  Expanded(
                                      child: Visibility(
                                    visible: valueVisible,
                                    child: Text(
                                      "${mypost['value']}",
                                      style: const TextStyle(
                                          color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                    ),
                                  ))
                                ],
                              ),
                            ),
                          ),
                        ),
                      ),
                    );
                  },
                );
        })

相关截图:
列表初始状态
点击后全部显示的状态


解决方案

方法一:用列表维护每个项的可见状态

核心思路是给每个列表项分配独立的状态变量,用布尔列表记录每个项的可见性:

  1. 在页面的State类中定义状态列表:
List<bool> _itemVisibility = [];
  1. 在StreamBuilder的builder中,数据加载完成后初始化列表(确保长度与文档数量一致,初始值可按需设置):
if (snapshot.hasData) {
  // 仅在数据变化时初始化,避免重复创建
  if (_itemVisibility.length != snapshot.data!.docs.length) {
    _itemVisibility = List.filled(snapshot.data!.docs.length, false);
  }
}
  1. 修改点击事件,切换对应索引的状态:
onTap: () {
  setState(() {
    _itemVisibility[index] = !_itemVisibility[index];
  });
},
  1. 更新Visibility组件的visible属性:
Visibility(
  visible: _itemVisibility[index],
  child: Text(
    "${mypost['value']}",
    style: const TextStyle(
        color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
  ),
)

修改后的完整代码片段:

// 页面State类中添加
List<bool> _itemVisibility = [];

// ...

body: StreamBuilder<QuerySnapshot>(
        stream: _keyService.getKeys(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            if (_itemVisibility.length != snapshot.data!.docs.length) {
              _itemVisibility = List.filled(snapshot.data!.docs.length, false);
            }
          }
          return !snapshot.hasData
              ? const Center(child: CircularProgressIndicator())
              : ListView.builder(
                  itemCount: snapshot.data!.docs.length,
                  itemBuilder: (context, index) {
                    DocumentSnapshot mypost = snapshot.data!.docs[index];

                    return Padding(
                      padding: EdgeInsets.all(size * 0.3),
                      child: InkWell(
                        onTap: () {
                          setState(() {
                            _itemVisibility[index] = !_itemVisibility[index];
                          });
                        },
                  
                        child: Container(
                          decoration: BoxDecoration(
                              color: ColorItems.mainColor,
                              border: Border.all(width: 5, color: Colors.grey),
                              borderRadius: BorderRadius.all(Radius.circular(20))),
                          child: Padding(
                            padding: EdgeInsets.all(size),
                            child: Container(
                              child: Row(
                                children: [
                                  Expanded(
                                    child: Text(
                                      "${mypost['key']}",
                                      style: const TextStyle(
                                          color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                    ),
                                  ),
                                  const Text(
                                    ": ",
                                    style:
                                        TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                  ),
                                  const SizedBox(
                                    width: 20,
                                  ),
                                  Expanded(
                                      child: Visibility(
                                    visible: _itemVisibility[index],
                                    child: Text(
                                      "${mypost['value']}",
                                      style: const TextStyle(
                                          color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                                    ),
                                  ))
                                ],
                              ),
                            ),
                          ),
                        ),
                      ),
                    );
                  },
                );
        })

方法二:将列表项封装为独立的StatefulWidget

把每个列表项做成独立的有状态组件,让每个组件自己维护可见状态,代码耦合度更低:

  1. 创建独立的列表项组件:
class KeyItemWidget extends StatefulWidget {
  final String keyText;
  final String valueText;
  final double size;

  const KeyItemWidget({
    super.key,
    required this.keyText,
    required this.valueText,
    required this.size,
  });

  @override
  State<KeyItemWidget> createState() => _KeyItemWidgetState();
}

class _KeyItemWidgetState extends State<KeyItemWidget> {
  bool _isVisible = false;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(widget.size * 0.3),
      child: InkWell(
        onTap: () {
          setState(() {
            _isVisible = !_isVisible;
          });
        },
        child: Container(
          decoration: BoxDecoration(
              color: ColorItems.mainColor,
              border: Border.all(width: 5, color: Colors.grey),
              borderRadius: const BorderRadius.all(Radius.circular(20))),
          child: Padding(
            padding: EdgeInsets.all(widget.size),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    widget.keyText,
                    style: const TextStyle(
                        color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                ),
                const Text(
                  ": ",
                  style:
                      TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(width: 20),
                Expanded(
                    child: Visibility(
                  visible: _isVisible,
                  child: Text(
                    widget.valueText,
                    style: const TextStyle(
                        color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                ))
              ],
            ),
          ),
        ),
      ),
    );
  }
}
  1. 修改原页面的ListView.builder部分:
ListView.builder(
  itemCount: snapshot.data!.docs.length,
  itemBuilder: (context, index) {
    DocumentSnapshot mypost = snapshot.data!.docs[index];
    return KeyItemWidget(
      keyText: "${mypost['key']}",
      valueText: "${mypost['value']}",
      size: size,
    );
  },
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 05:24:12