Flutter - 如何更新用于构建 ListView 的 Future/List 的状态(或值?)(通过 FutureBuilder)

use*_*281 5 listview dart flutter flutter-layout flutter-futurebuilder

我在下面粘贴了相关代码,但您也许可以根据我的伪解释来回答。

我使用 FutureBuilder 来构建列表视图。

  • 我首先使用 init() 异步 HTTP 调用 API,并将其解析为映射以表示 json 结果的对象列表(位置)。
  • 然后,该位置列表将返回到Future<List<Location>> _listFuture变量中(这是 FutureBuilder 的未来)。
  • 一旦 future“返回”或“完成”,FutureBuilder 就会启动并使用 ListView.builder/Container/ListTile 循环并构建列表。
  • 在某些时候,我需要一个 onTap() 处理程序(在 ListTile 中)来更改所选列表项的背景颜色。
  • 为了支持这一点,我在 Location 类(保存 JSON 响应)中有一个 backgroundColor 成员,对于所有项目,我默认为“#fc7303”(假设所有内容最初始终处于未选中状态)。然后我想将 onTap() 中选择的任何内容的背景更改为“#34bdeb”。
  • 我假设我可以调用 setState() ,这将触发刷新,并且新的背景颜色将在重绘时被注意到/使用。

问题是 ListView/Contrainer/ListTile 是由

Future<List<Location>>
Run Code Online (Sandbox Code Playgroud)

。我可以将“tapped”索引传递给我的 ontap 处理程序,但我不相信我可以让我的 _changeBackground() 只需更新所选索引的 backgroundColor 值并调用 setState() 因为您无法直接访问/更新这样的未来(我收到错误ERROR: The operator '[]' isn't defined for the class 'Future<List<Location>>'.

我不确定我采取了正确的方法。在这种情况下,我想理论上我总是可以将“背景”颜色跟踪分离到一个新的单独列表中(在未来之外),并使用 onTap() 中的对齐索引以这种方式跟踪/引用它。

但是,我不确定这总是有效。将来,我可能需要实际更改将来返回的值/状态。例如,考虑一下我是否希望能够单击列表项并更新“companyName”。在这种情况下,我将直接更改未来存储的值。我想我可以从技术上将新名称发送到服务器并以这种方式完全刷新列表,但这似乎效率低下(如果他们决定“取消”而不保存更改怎么办?)。

任何帮助表示赞赏。谢谢!

这个类实际上保存了列表的相关数据

// Location
class Location {

  // members
  String locationID;
  String locationName;
  String companyName;
  String backgroundColor = 'fc7303';

  // constructor?
  Location({this.locationID, this.locationName, this.companyName});

  // factory?
  factory Location.fromJson(Map<String, dynamic> json) {
    return Location(
      locationID: json['locationID'],
      locationName: json['locationName'],
      companyName: json['companyName'],
    );

  }

}
Run Code Online (Sandbox Code Playgroud)

此类是具有“结果”(成功/错误)消息的父级 json 响应。它将上面的类实例化为列表来跟踪实际的公司/位置记录

//jsonResponse
class jsonResponse{

  String result;
  String resultMsg;
  List<Location> locations;

  jsonResponse({this.result, this.resultMsg, this.locations});

  factory jsonResponse.fromJson(Map<String, dynamic> parsedJson){

    var list = parsedJson['resultSet'] as List;
    List<Location> locationList = list.map((i) => Location.fromJson(i)).toList();
    return jsonResponse(
        result: parsedJson['result'],
        resultMsg: parsedJson['resultMsg'],
        locations: locationList
    );
  }

} // jsonResponse
Run Code Online (Sandbox Code Playgroud)

这是使用上面的类来解析 API 数据并创建 ListView 的状态和有状态小部件

class locationsApiState extends State<locationsApiWidget> {

  // list to track AJAX results
  Future<List<Location>> _listFuture;

  // init - set initial values
  @override
  void initState() {
    super.initState();
    // initial load
    _listFuture = updateAndGetList();
  }

  Future<List<Location>> updateAndGetList() async {

    var response = await http.get("http://XXX.XXX.XXX.XXX/api/listCompanies.php");
    if (response.statusCode == 200) {
      var r1 = json.decode(response.body);
      jsonResponse r = new jsonResponse.fromJson(r1);
      return r.locations;
    } else {
      throw Exception('Failed to load internet');
    }

  }

  _changeBackground(int index){
    print("in changebackground(): ${index}");       // this works!
    _listFuture[index].backgroundColor = '34bdeb';   // ERROR: The operator '[]' isn't defined for the class 'Future<List<Location>>'.
  }

  // build() method
  @override
  Widget build(BuildContext context) {

    return new FutureBuilder<List<Location>>(
        future: _listFuture,
        builder: (context, snapshot){

          if (snapshot.connectionState == ConnectionState.waiting) {
            return new Center(
              child: new CircularProgressIndicator(),
            );
          } else if (snapshot.hasError) {
            return new Text('Error: ${snapshot.error}');
          } else {
            final items = snapshot.data;
            return new Scrollbar(
              child: new RefreshIndicator(
                  child: ListView.builder(
                    physics: const AlwaysScrollableScrollPhysics(),
                    //Even if zero elements to update scroll
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return
                        Container(
                            color: HexColor(items[index].backgroundColor),
                            child:
                            ListTile(
                              title: Text(items[index].companyName),
                              onTap: () {
                                print("Item at $index is ${items[index].companyName}");
                                _changeBackground(index);
                              }  // onTap
                            )
                        );
                    },
                  ),
                  onRefresh: () {
                    // implement later
                    return;
                  } // refreshList,
              ),
            );
          }// else
        } // builder
    ); // FutureBuilder
  } // build
} // locationsApiState class


class locationsApiWidget extends StatefulWidget {
  @override
  locationsApiState createState() => locationsApiState();
}
Run Code Online (Sandbox Code Playgroud)

用于将十六进制转换为整数颜色的辅助类(取自 stackoverflow 上的某处)

class HexColor extends Color {
  static int _getColorFromHex(String hexColor) {
    hexColor = hexColor.toUpperCase().replaceAll("#", "");
    if (hexColor.length == 6) {
      hexColor = "FF" + hexColor;
    }
    return int.parse(hexColor, radix: 16);
  }

  HexColor(final String hexColor) : super(_getColorFromHex(hexColor));
}
Run Code Online (Sandbox Code Playgroud)

谢谢!

spe*_*ter 2

我建议从您的位置类别中删除背景颜色,并将选择的位置存储在您的州中。这样,选择项目时您的位置列表就不需要更改。我还会为您的位置项创建一个 StatelessWidget,它将设置背景颜色,具体取决于是否选择它。所以:

// for the LocationItem widget callback
typedef void tapLocation(int index);

class locationsApiState extends State<locationsApiWidget> {

  // list to track AJAX results
  Future<List<Location>> _listFuture;
  final var selectedLocationIndices = Set<int>();

  // init - set initial values
  @override
  void initState() {
    super.initState();
    // initial load
    _listFuture = updateAndGetList();
  }

  Future<List<Location>> updateAndGetList() async {

    var response = await http.get("http://XXX.XXX.XXX.XXX/api/listCompanies.php");
    if (response.statusCode == 200) {
      var r1 = json.decode(response.body);
      jsonResponse r = new jsonResponse.fromJson(r1);
      return r.locations;
    } else {
      throw Exception('Failed to load internet');
    }
  }

  void _toggleLocation(int index) {
    if (selectedLocationIndices.contains(index))
      selectedLocationIndices.remove(index);
    else
      selectedLocationIndices.add(index);
  }

  // build() method
  @override
  Widget build(BuildContext context) {

    return new FutureBuilder<List<Location>>(
        future: _listFuture,
        builder: (context, snapshot){

          if (snapshot.connectionState == ConnectionState.waiting) {
            return new Center(
              child: new CircularProgressIndicator(),
            );
          } else if (snapshot.hasError) {
            return new Text('Error: ${snapshot.error}');
          } else {
            final items = snapshot.data;
            return new Scrollbar(
              child: new RefreshIndicator(
                  child: ListView.builder(
                    physics: const AlwaysScrollableScrollPhysics(),
                    //Even if zero elements to update scroll
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return LocationItem(
                        isSelected: selectedLocationIndices.contains(index),
                        onTap: () => setState({
                          _toggleLocation(index);
                        })
                      );
                    },
                  ),
                  onRefresh: () {
                    // implement later
                    return;
                  } // refreshList,
              ),
            );
          }// else
        } // builder
    ); // FutureBuilder
  } // build
} // locationsApiState class


class locationsApiWidget extends StatefulWidget {
  @override
  locationsApiState createState() => locationsApiState();
}
Run Code Online (Sandbox Code Playgroud)

以及项目列表条目:

class LocationItem extends StatelessWidget {

  final bool isSelected;
  final Function tapLocation;

  const LocationItem({@required this.isSelected, @required this.tapLocation, Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: isSelected ? HexColor('34bdeb') : HexColor('fc7303'),
      child: ListTile(
        title: Text(items[index].companyName),
        onTap: () => tapLocation() // onTap
      )
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

请原谅,我无法编译它,所以我希望它是正确的。但我认为您明白了:让 Stateful 小部件单独跟踪选定的位置,并让该位置决定在重建时如何呈现自身。