Flutter动态ExpansionTile

8

寻求动态构建expansionTile列表的指导。我成功地通过JSON API动态构建了Listview,但找不到任何构建expansionTile的示例。我的API调用有一个顶级以及每个顶级的另一个调用来带回扩展列表。有人有这方面的例子吗?我找到了静态的例子,但不清楚如何使它动态化。

以下是我编写的一些代码。我可以看到Tile标题部分,并可以看到json进入Tile身体,但无法弄清如何在身体中获取正确的列表标题名称,无论我尝试将其设置为什么都不起作用。有什么想法吗?

import 'dart:async';
import 'package:intl/intl.dart';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
//import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
//import 'package:cswauthapp/models.dart';
import 'package:flutter/foundation.dart';
import 'dart:convert';

var jsonCodec = const JsonCodec();
List<Exp> myReasonList;
List myDCList;
int mycount = 0;

void main() {
  runApp(new MyApp());

}



class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'ExpansionTile Test',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

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

    _getData();
    //_getSpecialty();
  }

  _getData() async {
    var _url = 'http://$baseurl:8080/support/dc/1';

    var http = createHttpClient();
    var response = await http.get(_url);

    var dc = await jsonCodec.decode(response.body);
    myDCList = await dc.toList();



    print('DC: '+myDCList.toString());

    if (mounted) {
      setState(() {
        //_dataReceived = true;
        mycount = myDCList.length;
      });
    }

  }

  Future _getChildren(int did) async {

    var _url2 = 'http://174.138.61.246:8080/support/dcreasons/$did';
    var http = createHttpClient();
    var response = await http.get(_url2);
    var reasons = await jsonCodec.decode(response.body);
    myReasonList = await reasons.toList();
    print('REASONS: '+ myReasonList.toString());


    return myReasonList;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('ExpansionTile Test'),
      ),
      body: new ListView.builder(
        itemBuilder: _itemBuilder,
        itemCount: mycount,
      ),
    );
  }

  Widget _itemBuilder(BuildContext context, int index) {
    Exp exp = getExp(index);
    return new ListChild(exp: exp,);
  }

  Exp getExp(int index) {
    return new Exp(
      myDCList[index]['dname'],
      _getChildren(myDCList[index]['did']),
    );
    //return new Specialties.fromMap(mylist[index]);

  }
}

class Exp {
  Exp(this.title, [this.children]);
  final String title;
  final Future<List<Exp>> children;
}


class ListChild extends StatefulWidget {
  ListChild({Key key, this.exp}) : super(key: key);

  final Exp exp;
  @override
  State createState() => new ListChildState();
}

class ListChildState extends State<ListChild> {
  //PageStorageKey<ListChildState> _key = new PageStorageKey(ListChild);
  @override
  Widget build(BuildContext context) {
    return new ExpansionTile(
      key: new PageStorageKey(ListChild),
      title: new Text(widget.exp.title),
      children: <Widget>[
        new Text(widget.exp.children.title),
      ],
    );
  }
}

请问您能否删除最后的编辑并提出一个新问题?否则,没有人会知道这个问题实际上是关于什么的。 - Rainer Wittmann
抱歉,我刚才删除了一个问题并添加了另一个问题。 - Robert
3个回答

22

针对您的评论和问题的编辑,我想自作主张写了一个可行的示例。请随意编辑或评论。我希望这正是您想要实现的。

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'ExpansionTile Test',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<http.Response> _responseFuture;

  @override
  void initState() {
    super.initState();
    _responseFuture = http.get('http://174.138.61.246:8080/support/dc/1');
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('ExpansionTile Test'),
      ),
      body: new FutureBuilder(
        future: _responseFuture,
        builder: (BuildContext context, AsyncSnapshot<http.Response> response) {
          if (!response.hasData) {
            return const Center(
              child: const Text('Loading...'),
            );
          } else if (response.data.statusCode != 200) {
            return const Center(
              child: const Text('Error loading data'),
            );
          } else {
            List<dynamic> json = JSON.decode(response.data.body);
            return new MyExpansionTileList(json);
          }
        },
      ),
    );
  }
}

class MyExpansionTileList extends StatelessWidget {
  final List<dynamic> elementList;

  MyExpansionTileList(this.elementList);

  List<Widget> _getChildren() {
    List<Widget> children = [];
    elementList.forEach((element) {
      children.add(
        new MyExpansionTile(element['did'], element['dname']),
      );
    });
    return children;
  }

  @override
  Widget build(BuildContext context) {
    return new ListView(
      children: _getChildren(),
    );
  }
}

class MyExpansionTile extends StatefulWidget {
  final int did;
  final String name;
  MyExpansionTile(this.did, this.name);
  @override
  State createState() => new MyExpansionTileState();
}

class MyExpansionTileState extends State<MyExpansionTile> {
  PageStorageKey _key;
  Future<http.Response> _responseFuture;

  @override
  void initState() {
    super.initState();
    _responseFuture =
        http.get('http://174.138.61.246:8080/support/dcreasons/${widget.did}');
  }

  @override
  Widget build(BuildContext context) {
    _key = new PageStorageKey('${widget.did}');
    return new ExpansionTile(
      key: _key,
      title: new Text(widget.name),
      children: <Widget>[
        new FutureBuilder(
          future: _responseFuture,
          builder:
              (BuildContext context, AsyncSnapshot<http.Response> response) {
            if (!response.hasData) {
              return const Center(
                child: const Text('Loading...'),
              );
            } else if (response.data.statusCode != 200) {
              return const Center(
                child: const Text('Error loading data'),
              );
            } else {
              List<dynamic> json = JSON.decode(response.data.body);
              List<Widget> reasonList = [];
              json.forEach((element) {
                reasonList.add(new ListTile(
                  dense: true,
                  title: new Text(element['reason']),
                ));
              });
              return new Column(children: reasonList);
            }
          },
        )
      ],
    );
  }
}

应用程序截图


1
这个完美地运行了,我甚至没有接近或采取错误的方法。谢谢你的帮助。 - Robert
在这里,您请求API两次,但另一方面将要显示在展开瓷砖中的JSON数据分配给列表,然后通过循环将每个值附加到小部件列表中。 - Jabir Ishaq
真的是一个很好的解决方案。我正在努力让它适用于多个级别。我将MyEspansionTileList作为ExpansionTile的子项,但在滚动和大小方面出现了错误。 - camillo777
不建议将小部件作为方法提取,例如 _getChildren()。尝试将其作为小部件提取。 - Mutlu Simsek

4

根据Rainer Wittmann的方法,我进行了修改以适应我的需求,并为Cloud Firestore实施,但我使用了streams而不是futures

Cloud Firestore的基本结构如下:

集合projects

  • 名称

  • 集合 surveys:

    • surveyName

解决方案:

class ProjectList extends StatelessWidget {
  ProjectList({this.firestore});

  final Firestore firestore;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: firestore.collection('projects').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) return const Text('Loading...');
        //final int projectsCount = snapshot.data.documents.length;
        List<DocumentSnapshot> documents = snapshot.data.documents;
        return ExpansionTileList(
          firestore: firestore,
          documents: documents,
        );
      },
    );
  }
}

class ExpansionTileList extends StatelessWidget {
  final List<DocumentSnapshot> documents;
  final Firestore firestore;

  ExpansionTileList({this.documents, this.firestore});

  List<Widget> _getChildren() {
    List<Widget> children = [];
    documents.forEach((doc) {
      children.add(
        ProjectsExpansionTile(
          name: doc['name'],
          projectKey: doc.documentID,
          firestore: firestore,
        ),
      );
    });
    return children;
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: _getChildren(),
    );
  }
}

class ProjectsExpansionTile extends StatelessWidget {
  ProjectsExpansionTile({this.projectKey, this.name, this.firestore});

  final String projectKey;
  final String name;
  final Firestore firestore;

  @override
  Widget build(BuildContext context) {
    PageStorageKey _projectKey = PageStorageKey('$projectKey');

    return ExpansionTile(
      key: _projectKey,
      title: Text(
        name,
        style: TextStyle(fontSize: 28.0),
      ),
      children: <Widget>[
        StreamBuilder(
            stream: firestore
                .collection('projects')
                .document(projectKey)
                .collection('surveys')

                .snapshots(),
            builder:
                (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
              if (!snapshot.hasData) return const Text('Loading...');
              //final int surveysCount = snapshot.data.documents.length;
              List<DocumentSnapshot> documents = snapshot.data.documents;

              List<Widget> surveysList = [];
              documents.forEach((doc) {
                PageStorageKey _surveyKey =
                    new PageStorageKey('${doc.documentID}');

                surveysList.add(ListTile(
                  key: _surveyKey,
                  title: Text(doc['surveyName']),
                ));
              });
              return Column(children: surveysList);
            })
      ],
    );
  }
}

希望这能帮助那些在云Firestore中迷失在嵌套集合中的人。
编程愉快!

1
我认为解决这个问题的最佳方式是使用FutureBuilder(可以参考这个答案)。我会为每个ExpansionTile标题实现一个FutureBuilder,以及第二个FutureBuilder用于每个ExpansionTile正文。
上述链接答案中的示例非常适用于您的用例。如果您需要更多帮助,请告诉我,我将尝试为您实现一个示例。

我更新了我的帖子,并附上了我编写并尝试使用的代码。有什么想法吗? - Robert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接