Flutter - 在列表视图中仅选择单个项目

Posted

技术标签:

【中文标题】Flutter - 在列表视图中仅选择单个项目【英文标题】:Flutter - select only single item in list view 【发布时间】:2021-02-13 00:55:18 【问题描述】:

在我的应用程序中,我正在生成一个 ListView,并且可以通过点击它们来突出显示项目。这很好用,我还有一个回调函数,它为我提供了刚刚选择的项目的密钥。我目前可以通过再次点击该项目来手动取消选择该项目,但最终会取消该功能。

我的问题是我希望一次只能选择一项。为了创建列表,我目前以列表的形式获取一些初始内容,生成图块并将它们添加到另一个列表中。然后我使用该列表来创建 ListView。我的计划是从新选择中回调,遍历图块列表并取消选择它们,然后突出显示新选择的图块并执行其他功能。我尝试了各种方法来告诉每个图块取消选择自己,但没有找到任何方法来处理每个图块。目前我收到错误:

类“OutlineTile”没有实例方法“取消选择”。 接收方:“OutlineTile”实例 尝试调用:deselect()

我尝试访问 tile 类中的方法并使用 setter,但到目前为止都没有成功。我对颤动很陌生,所以它可能是我缺少的一些简单的东西。我之前的经验是使用 Actionscript,这个系统可以正常工作,只要它是公共方法,我就可以轻松访问对象(在本例中为磁贴)的方法。

我很高兴有另一种方法来取消选择旧项目或找到一种方法来访问磁贴中的方法。 挑战是在没有自己点击的情况下使图块不突出显示,而是在点击不同的图块时显示。

我的父类中的代码如下:

class WorkingDraft extends StatefulWidget 
  final String startType;
  final String name;
  final String currentContent;
  final String currentID;
  final List startContent;

  WorkingDraft(
      this.startType,
      this.name,
      this.currentContent,
      this.currentID,
      this.startContent);

  @override
  _WorkingDraftState createState() => _WorkingDraftState();


class _WorkingDraftState extends State<WorkingDraft> 
  final _formKey = GlobalKey<FormState>();
  final myController = TextEditingController();
  //String _startType;
  String _currentContent = "";
  String _name = "Draft";
  List _startContent = [];
  List _outLineTiles = [];
  int _counter = 0;

  @override
  void dispose() 
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  

  void initState() 
    super.initState();

    _currentContent = widget.currentContent;
    _name = widget.name;
    _startContent = widget.startContent;
    _counter = 0;

    _startContent.forEach((element) 
      _outLineTiles.add(OutlineTile(
        key: Key("myKey$_counter"),
        outlineName: element[0],
        myContent: element[1],
        onTileSelected: clearHilights,
      ));
      _counter++;
    );
  

  dynamic clearHilights(Key myKey) 
    _outLineTiles.forEach((element) 
      element.deselect();  // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
      Key _foundKey = element.key;
      print("Element Key $_foundKey");
    );
  
.......

在小部件构建脚手架中进一步向下:

      child: ListView.builder(
        itemCount: _startContent.length,
        itemBuilder: (context, index) 
          return _outLineTiles[index];
        ,
      ),

那么tile类如下:

class OutlineTile extends StatefulWidget 
  final Key key;
  final String outlineName;
  final Icon myIcon;
  final String myContent;
  final Function(Key) onTileSelected;

  OutlineTile(
      this.key,
      this.outlineName,
      this.myIcon,
      this.myContent,
      this.onTileSelected);

  @override
  _OutlineTileState createState() => _OutlineTileState();


class _OutlineTileState extends State<OutlineTile> 
  Color color;
  Key _myKey;

  @override
  void initState() 
    super.initState();

    color = Colors.transparent;
  

  bool _isSelected = false;
  set isSelected(bool value) 
    _isSelected = value;
    print("set is selected to $_isSelected");
  

  void changeSelection() 
    setState(() 
      _myKey = widget.key;
      _isSelected = !_isSelected;
      if (_isSelected) 
        color = Colors.lightBlueAccent;
       else 
        color = Colors.transparent;
      
    );
  

  void deselect() 
    setState(() 
      isSelected = false;
      color = Colors.transparent;
    );
  

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(top: 4.0),
      child: Row(
        children: [
          Card(
            elevation: 10,
            margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
            child: SizedBox(
              width: 180,
              child: Container(
                color: color,
                child: ListTile(
                  title: Text(widget.outlineName),
                  onTap: () 
                    if (widget.outlineName == "Heading") 
                      Text("Called Heading");
                     else (widget.outlineName == "Paragraph") 
                      Text("Called Paragraph");
                    widget.onTileSelected(_myKey);
                    changeSelection();
                  ,
                ),

         ........ 

感谢您的帮助。

修改后的代码示例和解释,构建一个完整的项目,来自这里:

按照 pimath 的建议,我创建了项目相关部分的完整可构建示例。

问题是我的列表视图中的图块更复杂,有几个元素,其中许多元素本身就是按钮,所以虽然 pimath 的解决方案适用于简单的文本图块,但我无法让它在我自己的项目中工作.我的方法是尝试从根本上做与 pimath 相同的事情,但是当我包含这些更复杂的图块时,它就无法工作了。

此示例项目由三个文件组成。 main.dart 只是调用项目并以我的主项目的方式传递一些虚拟数据。 working_draft.dart 是这个问题的核心。以及构成图块的对象 outline_tile.dart。

在工作草案中,我有一个函数,它返回一个更新的图块列表,该列表应该显示选择了哪个图块(以及稍后来自其他按钮的任何其他更改)。这在第一次进入屏幕时被调用。当瓷砖被点击时,它使用一个回调函数来重绘 working_draft 类,但这似乎没有像我期望的那样重绘列表。任何进一步的指导将不胜感激。

类是:

第一类是 main.dart:

import 'package:flutter/material.dart';
import 'package:listexp/working_draft.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
        home: WorkingDraft(
      startType: "Basic",
      name: "Draft",
      currentID: "anID",
      startContent: [
        ["Heading", "New Heading"],
        ["Paragraph", "New Text"],
        ["Image", "placeholder"],
        ["Signature", "placeholder"]
      ],
    ));
  

下一个文件是 working_draft.dart:

import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';

class WorkingDraft extends StatefulWidget 
  final String startType;
  final String name;
  final String currentContent;
  final String currentID;
  final List startContent;
  final int selectedIndex;

  WorkingDraft(
      this.startType,
      this.name,
      this.currentContent,
      this.currentID,
      this.startContent,
      this.selectedIndex);

  @override
  _WorkingDraftState createState() => _WorkingDraftState();


class _WorkingDraftState extends State<WorkingDraft> 
  int selectedIndex;
  String _currentContent = "";
  String _name = "Draft";
  List _startContent = [];
  var _outLineTiles = [];
  int _counter = 0;
  int _selectedIndex;
  bool _isSelected;

  dynamic clearHilights(int currentIndex) 
    setState(() 
      _selectedIndex = currentIndex;
    );
  

  updatedTiles() 
    if (_selectedIndex == null) 
      _selectedIndex = 0;
    

    _currentContent = widget.currentContent;
    _name = widget.name;
    _startContent = widget.startContent;
    _counter = 0;
    _outLineTiles = [];
    _startContent.forEach((element) 
      _isSelected = _selectedIndex == _counter ? true : false;
      _outLineTiles.add(OutlineTile(
        key: Key("myKey$_counter"),
        outlineName: element[0],
        myContent: element[1],
        myIndex: _counter,
        onTileSelected: clearHilights,
        isSelected: _isSelected,
      ));
      _counter++;
    );
  

  @override
  Widget build(BuildContext context) 
    updatedTiles();
    return Scaffold(
        body: Center(
      child: Column(children: [
        SizedBox(height: 100),
        Text("Outline", style: new TextStyle(fontSize: 15)),
        Container(
          height: 215,
          width: 300,
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.lightGreenAccent,
              width: 2,
            ),
            borderRadius: BorderRadius.circular(2),
          ),
          child: ListView.builder(
            itemCount: _startContent.length,
            itemBuilder: (context, index) 
              return _outLineTiles[index];
            ,
          ),
        ),
      ]),
    ));
  

最后是outline_tile.dart

import 'package:flutter/material.dart';

class OutlineTile extends StatefulWidget 
  final Key key;
  final String outlineName;
  final Icon myIcon;
  final String myContent;
  final int myIndex;
  final Function(int) onTileSelected;
  final bool isSelected;

  OutlineTile(
      this.key,
      this.outlineName,
      this.myIcon,
      this.myContent,
      this.myIndex,
      this.onTileSelected,
      this.isSelected);

  @override
  _OutlineTileState createState() => _OutlineTileState();


class _OutlineTileState extends State<OutlineTile> 
  Color color;
  // Key _myKey;
  bool _isSelected;

  @override
  void initState() 
    super.initState();

    _isSelected = widget.isSelected;

    if (_isSelected == true) 
      color = Colors.lightBlueAccent;
     else 
      color = Colors.transparent;
    
  

  void deselect() 
    setState(() 
      _isSelected = widget.isSelected;

      if (_isSelected == true) 
        color = Colors.lightBlueAccent;
       else 
        color = Colors.transparent;
      
    );
  

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(top: 4.0),
      child: Row(
        children: [
          Card(
            elevation: 10,
            margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
            child: SizedBox(
              width: 180,
              child: Container(
                color: color,
                child: ListTile(
                  title: Text(widget.outlineName),
                  onTap: () 
                    if (widget.outlineName == "Heading") 
                      Text("Called Heading");
                     else if (widget.outlineName == "Paragraph") 
                      Text("Called Paragraph");
                     else if (widget.outlineName == "Signature") 
                      Text("Called Signature");
                     else 
                      Text("Called Image");
                    
                    var _myIndex = widget.myIndex;

                    widget.onTileSelected(_myIndex);
                    deselect();
                  ,
                ),
              ),
            ),
          ),
          SizedBox(
            height: 60,
            child: Column(
              children: [
                SizedBox(
                  height: 20,
                  child: IconButton(
                      iconSize: 30,
                      icon: Icon(Icons.arrow_drop_up),
                      onPressed: () 
                        print("Move Up");
                      ),
                ),
                SizedBox(height: 5),
                SizedBox(
                  height: 20,
                  child: IconButton(
                      iconSize: 30,
                      icon: Icon(Icons.arrow_drop_down),
                      onPressed: () 
                        print("Move Down");
                      ),
                ),
              ],
            ),
          ),
          SizedBox(
            height: 60,
            child: Column(
              children: [
                SizedBox(
                  height: 20,
                  child: IconButton(
                      iconSize: 20,
                      icon: Icon(Icons.add_box),
                      onPressed: () 
                        print("Add another");
                      ),
                ),
                SizedBox(
                  height: 10,
                ),
                SizedBox(
                  height: 20,
                  child: IconButton(
                      iconSize: 20,
                      icon: Icon(Icons.delete),
                      onPressed: () 
                        print("Delete");
                      ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  

再次感谢

【问题讨论】:

【参考方案1】:

无需手动取消选择磁贴,只需跟踪当前选择的磁贴即可。

我为你做了一个简单的例子。当我们点击一​​个磁贴时,我们只是将选定的索引设置为我们点击的索引,每个磁贴都会查看它以查看它是否是当前选定的磁贴。

import 'package:flutter/material.dart';

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(body: Home()),
    );
  


class Home extends StatefulWidget 
  @override
  _HomeState createState() => _HomeState();


class _HomeState extends State<Home> 
  int selectedIndex;

  @override
  Widget build(BuildContext context) 
    return ListView.builder(
      itemCount: 10,
      itemBuilder: (context, index) 
        return ListTile(
          title: Text('Item: $index'),
          tileColor: selectedIndex == index ? Colors.blue : null,
          onTap: () 
            setState(() 
              selectedIndex = index;
            );
          ,
        );
      ,
    );
  

【讨论】:

谢谢。 Yes I considered that but each tile is highlighted when selected so when a new one is selected I still need to go back to the previous tile and 'deselect' that one to remove the highlight.您的方法将允许我只取消选择一个图块而不是循环遍历所有图块,这更有效,但我仍然需要一种方法来取消选择旧图块而不直接点击它。那有意义吗?谢谢。 你运行了这个例子吗?这可确保一次只选择 1 个图块。没有“取消选择”。 嗨,是的,我已经运行了您的示例,是的,它一次只选择一个图块。然而到目前为止,我还没有设法让它在我自己的代码中工作,这更复杂,因为每个图块都有几个元素,其中许多元素可以独立交互。明天我必须做一些其他的事情,但在那之后我计划尝试使用您的代码作为示例的基础,更清楚地显示我在没有完整项目的情况下遇到的问题。希望这可以帮助我找到哪里出了问题,或者给我一些足够小的东西,我可以和你分享。再次感谢您的帮助。 phimath,我现在已经能够创建一个完整的版本了。我曾尝试使用您的方法,但使用更复杂的瓷砖我无法让它工作。谢谢。 页面打开时如何在列表视图中进行默认选择?

以上是关于Flutter - 在列表视图中仅选择单个项目的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 是不是有上下文操作栏,用于从列表视图中选择多个项目

如何在iOS,Objective C的tableview中的每个部分中仅实现单个单元格选择

Flutter 在 ListView 中仅重建一行

是否可以在颤动的可重新排序的列表视图中禁用对单个项目的重新排序?

Flutter Provider-重建列表项而不是列表视图

Android如何从同一个数组中仅显示列表视图中的某些项目?