Flutter滚动到动态ListView的底部
我已经查找并找到了几个解决方案,但似乎都不适合我的配置,需要一些帮助。我将把所有的代码放在这里,看看是否有人知道在哪里应用ScrollController。我已经尝试在原始的ListView上,但是我从futureResponse中动态构建ListView构建器下的其他项目。\nimport \'dart:async\';\nimport \'dart:io\';\nimport \'dart:typed_data\';\nimport \'dart:convert\';\nimport \'package:http/http.dart\' as http;\nimport \'package:intl/intl.dart\';\nimport \'package:flutter/material.dart\';\nimport \'package:flutter/services.dart\';\nimport \'package:shared_preferences/shared_preferences.dart\';\nimport \'package:cswauthapp/models.dart\';\nimport \'package:cswauthapp/ChatConvoDetail.dart\';\nimport \'package:cswauthapp/Settings.dart\' as Admin;\nimport \'package:cswauthapp/HomePage.dart\' as HomePage;\nimport \'package:cswauthapp/main.dart\' as MyHomePage;\nimport \'package:cswauthapp/PostAuthHome.dart\' as PostAuthHome;\nimport \'package:image_picker/image_picker.dart\';\nimport \'package:cswauthapp/ShowPic.dart\';\nimport \'package:path/path.dart\' as path;\nimport \'package:video_player/video_player.dart\';\nimport \'package:cswauthapp/vplayer.dart\' as vplayer;\nclass ChatDivided extends StatefulWidget {\n ChatDivided({Key key, this.title, this.mychat}) : super(key: key);\n static const String routeName = \"/ChatDivided\";\n final ChatList mychat;\n final String title;\n @override\n _ChatDividedState createState() => new _ChatDividedState();\n}\nclass _ChatDividedState extends State {\n SharedPreferences prefs;\n int oid = 0;\n int pid = 0;\n int authlevel = 0;\n bool admin = false;\n int type = 0;\n String msgid = \'\';\n List chatlist;\n int listcount = 0;\n bool grpmsg = true;\n String sender = \'\';\n String receiver = \'\';\n String message = \'\';\n String oname = \'\';\n String pname = \'\';\n String sendname;\n String receivename;\n String replyto = \'\';\n String replyfrom = \'\';\n String replysub = \'\';\n final TextEditingController _newreplycontroller = new TextEditingController();\n String myfcmtoken = \'NONE\';\n Future _responseFuture;\n var _urlDates = \'\';\n Future _imageFile;\n String myimage;\n String myvideo;\n File myimagefile;\n File myvidfile;\n Future myimagelength;\n String myext;\n VideoPlayerController vcontroller;\n bool isImage = false;\n bool isVideo = false;\n //ScrollController scontroller = new ScrollController();\n _getPrefs() async {\n prefs = await SharedPreferences.getInstance();\n if (mounted) {\n setState(() {\n oid = prefs.getInt(\'oid\');\n pid = prefs.getInt(\'pid\');\n authlevel = prefs.getInt(\'authlevel\');\n admin = prefs.getBool(\'admin\');\n type = 1;\n msgid = widget.mychat.msgkey;\n if (widget.mychat.grpid == \'0\') {\n grpmsg = false;\n } else {\n grpmsg = true;\n }\n oname = widget.mychat.oname;\n pname = widget.mychat.pname;\n myfcmtoken = prefs.getString(\'fcmtoken\');\n if (authlevel == 0) {\n sender = \'o\';\n receiver = \'p\';\n sendname = widget.mychat.oname;\n receivename = widget.mychat.pname;\n } else if (authlevel == 1) {\n sender = \'p\';\n receiver = \'o\';\n sendname = widget.mychat.pname;\n receivename = widget.mychat.oname;\n }\n //_getChats();\n });\n }\n }\n @override\n void initState() {\n super.initState();\n //controller = new TabController(length: 4, vsync: this);\n _getPrefs();\n _urlDates = \'http://$baseurl/chat/messages/getdates/${widget.mychat.msgkey}\';\n _responseFuture = http.get(_urlDates, headers: getAuthHeader());\n }\n var jsonCodec = const JsonCodec();\n var _focusnode = new FocusNode();\n _getChats() async {\n var _url = \'http://$baseurl/chat/messages/getdates/$msgid\';\n var http = createHttpClient();\n var response = await http.get(_url, headers: getAuthHeader());\n var chats = await jsonCodec.decode(response.body);\n if (mounted) {\n setState(() {\n chatlist = chats.toList();\n listcount = chatlist.length;\n //replysub = \'Re: \' + chatlist[0][\'sub\'];\n });\n }\n }\n Future _onRefresh() {\n Completer completer = new Completer();\n Timer timer = new Timer(new Duration(seconds: 1), () {\n setState(() {\n _responseFuture = http.get(_urlDates, headers: getAuthHeader());\n print(\'RUNNING LOAD AFTER REFRESH AGAIN\');\n });\n completer.complete();\n });\n return completer.future;\n }\n Future doImageString() async {\n return (await _imageFile).path.substring((await _imageFile).path.length - 3);\n }\n @override\n Widget build(BuildContext context) {\n Widget mytitle;\n if (grpmsg) {\n mytitle = new Row(\n mainAxisAlignment: MainAxisAlignment.start,\n children: [\n new Icon(Icons.people),\n new Text(\' \'),\n new Text(widget.mychat.referralname)\n ],\n );\n } else {\n mytitle = new Row(\n mainAxisAlignment: MainAxisAlignment.start,\n children: [\n new Icon(Icons.person),\n new Text(\' \'),\n new Text(widget.mychat.referralname)\n ],\n );\n }\n var _children = [\n new Flexible(\n child: new RefreshIndicator(\n child: new FutureBuilder(\n future: _responseFuture,\n builder: (BuildContext context, AsyncSnapshot response) {\n if (!response.hasData) {\n return const Center(\n //child: const Text(\'Loading Dates...\'),\n child: const CircularProgressIndicator(),\n );\n } else if (response.data.statusCode != 200) {\n return const Center(\n child: const Text(\'Error loading data\'),\n );\n } else {\n List json = JSON.decode(response.data.body);\n return new MyChatList(json);\n }\n },\n ),\n onRefresh: _onRefresh),\n ),\n new Container(\n alignment: Alignment.bottomLeft,\n padding: new EdgeInsets.only(left: 10.0),\n child: new FutureBuilder(\n future: _imageFile,\n builder: (BuildContext context, AsyncSnapshot snapshot) {\n if (snapshot.connectionState == ConnectionState.done) {\n //return new Image.file(snapshot.data);\n myimagefile = snapshot.data;\n myext = path.extension(myimagefile.path);\n if (myext == \'.jpg\') {\n isImage = true;\n return new Column(\n mainAxisAlignment: MainAxisAlignment.start,\n children: [\n new Container(\n alignment: Alignment.bottomLeft,\n width: 150.0,\n child: new Image.file(snapshot.data),\n ),\n new FlatButton(\n onPressed: _doClear, child: new Text(\'Clear Image\'))\n ],\n );\n } else {\n isVideo = true;\n myvidfile = new File(snapshot.data.path.replaceAll(\'file://\', \'\'));\n vcontroller = new VideoPlayerController(myimagefile.path)..initialize();\n return new Column(\n mainAxisAlignment: MainAxisAlignment.start,\n children: [\n new Container(\n alignment: Alignment.bottomLeft,\n width: 300.0,\n child: new vplayer.VideoCard(controller: vcontroller, title: widget.mychat.referralname,subtitle: \'Video\',),\n ),\n new FlatButton(\n onPressed: _doClear, child: new Text(\'Clear Video\'))\n ],\n );\n }\n } else {\n return const Text(\'\');\n }\n })\n ),\n new Divider(\n height: 5.0,\n color: Colors.grey,\n ),\n new Row(\n crossAxisAlignment: CrossAxisAlignment.end,\n children: [\n new Container(\n alignment: Alignment.bottomLeft,\n //width: 50.0,\n child: new IconButton(\n icon: new Icon(Icons.add_a_photo),\n onPressed: _pickImage,\n alignment: Alignment.bottomLeft,\n ),\n ),\n new Flexible(\n child: new Container(\n alignment: Alignment.center,\n //width: 350.0,\n child: new TextField(\n decoration: const InputDecoration(\n hintText: \'Reply\',\n labelText: \'Reply:\',\n ),\n autofocus: false,\n focusNode: _focusnode,\n maxLines: 1,\n controller: _newreplycontroller,\n keyboardType: TextInputType.text,\n ),\n ),\n ),\n new Container(\n alignment: Alignment.bottomRight,\n //width: 50.0,\n child: new IconButton(\n icon: new Icon(Icons.send),\n onPressed: _sendReply,\n alignment: Alignment.centerRight,\n disabledColor: Colors.grey,\n )),\n ],\n ),\n ];\n return new Scaffold(\n appBar: new AppBar(\n title: mytitle,\n actions: getAppBarActions(context),\n ),\n body: new Column(\n children: _children,\n ),\n );\n }\n DateTime getDateDiv(int index) {\n DateTime msgdate = DateTime.parse(chatlist[index][\'chatdate\']).toLocal();\n return msgdate;\n }\n _doClear() {\n setState(() {\n _imageFile = null;\n });\n }\n _pickImage() async {\n await setState(() {\n _imageFile = ImagePicker.pickImage(maxWidth: 600.0);\n });\n }\n _sendReply() {\n if (_newreplycontroller.text.isEmpty && myimagefile == null) {\n showDialog(\n context: context,\n child: new AlertDialog(\n content: new Text(\"There is no message to submit\"),\n actions: [\n new FlatButton(\n child: const Text(\'OK\'),\n onPressed: () {\n Navigator.pop(context, false);\n }),\n ],\n ),\n );\n } else {\n TextInputAction.done;\n DateTime dateSubmit = new DateTime.now();\n if (myimagefile != null) {\n if (isImage) {\n List imageBytes = myimagefile.readAsBytesSync();\n myimage = BASE64.encode(imageBytes);\n }\n if (isVideo) {\n List imageBytes = myvidfile.readAsBytesSync();\n myvideo = BASE64.encode(imageBytes);\n }\n } else {\n myimage = \'\';\n myvideo = \'\';\n }\n var mymessage = _newreplycontroller.text;\n ChatMessage mychat = new ChatMessage(\n widget.mychat.msgkey,\n widget.mychat.referralname,\n replysub,\n oid,\n oname,\n pid,\n pname,\n sender,\n sendname,\n receiver,\n receivename,\n mymessage,\n dateSubmit.toString(),\n widget.mychat.grpid,\n widget.mychat.prid,\n myfcmtoken,\n myimage,\n myvideo,\n myext\n );\n _doSendReply(mychat);\n }\n }\n _doSendReply(mychat) async {\n var json = jsonCodec.encode(mychat);\n var _url = \'http://$baseurl/chat/messages/send\';\n //var request = new http.MultipartRequest(\'POST\', _url)\n var http = createHttpClient();\n var response = await http.post(_url, body: json, headers: getJSONHeader());\n var chatresp = await jsonCodec.decode(response.body);\n if (chatresp.contains(\'GOOD\')) {\n setState(() {\n _responseFuture = http.get(_urlDates, headers: getAuthHeader());\n _doClear();\n print(\'RUNNING LOAD AFTER SEND AGAIN\');\n });\n _newreplycontroller.text = \'\';\n _focusnode.unfocus();\n } else if (chatresp.contains(\'EMPTY\')) {\n showDialog(\n context: context,\n child: new AlertDialog(\n content: new Text(\"There is no message to submit\"),\n actions: [\n new FlatButton(\n child: const Text(\'OK\'),\n onPressed: () {\n Navigator.pop(context, false);\n }),\n ],\n ),\n );\n } else {}\n }\n}\nclass MyChatList extends StatelessWidget {\n final List elementList;\n static ScrollController _scrollController;\n MyChatList(this.elementList);\n List _getChildren() {\n List children = [];\n elementList.forEach((element) {\n children.add(\n new MyChatWidget(\n datediv: element[\'msgdate\'], msgkey: element[\'msgkey\']),\n );\n //_scrollController.animateTo(0.0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);\n });\n return children;\n }\n @override\n Widget build(BuildContext context) {\n //_scrollController.position.maxScrollExtent;\n return new ListView(\n shrinkWrap: true,\n controller: _scrollController,\n reverse: false,\n children: _getChildren(),\n );\n }\n}\nclass MyChatWidget extends StatefulWidget {\n MyChatWidget({Key key, this.datediv, this.msgkey}) : super(key: key);\n final String datediv;\n final String msgkey;\n @override\n _MyChatWidgetState createState() => new _MyChatWidgetState();\n}\nclass _MyChatWidgetState extends State {\n List messagelist;\n int messagecount = 0;\n var jsonCodec = const JsonCodec();\n var mydate = \'\';\n var _urlMessages = \'\';\n PageStorageKey _key;\n Future _responseFuture;\n List messList;\n var mybytes;\n File myimageview;\n Image newimageview;\n String imgStr;\n String vidStr;\n @override\n void initState() {\n super.initState();\n if (new DateFormat.yMd().format(DateTime.parse(widget.datediv)) ==\n new DateFormat.yMd().format(new DateTime.now())) {\n mydate = \'Today\';\n } else {\n mydate = new DateFormat.yMMMEd().format(DateTime.parse(widget.datediv));\n }\n DateChatMessage dcm = new DateChatMessage(widget.msgkey, widget.datediv.toString());\n var json = jsonCodec.encode(dcm);\n _urlMessages = \'http://loop-dev.clinicalsoftworks.com/chat/messages/getbydate\';\n _responseFuture = http.post(_urlMessages, body: json, headers: getAuthHeader());\n //controller = new TabController(length: 4, vsync: this);\n //_getMessages();\n }\n @override\n Widget build(BuildContext context) {\n _key = new PageStorageKey(\'${widget.datediv.toString()}\');\n VideoPlayerController vcontroller;\n return new Column(\n children: [\n new Container(\n child: new Text(\n mydate,\n textAlign: TextAlign.left,\n style: new TextStyle(\n color: Colors.grey,\n fontWeight: FontWeight.bold,\n ),\n ),\n alignment: Alignment.centerLeft,\n padding: new EdgeInsets.only(left: 10.0),\n ),\n new Container(\n child: new Divider(\n height: 5.0,\n color: Colors.grey,\n ),\n padding: new EdgeInsets.only(left: 10.0, right: 10.0),\n ),\n /**/\n new FutureBuilder(\n future: _responseFuture,\n builder:\n (BuildContext context, AsyncSnapshot response) {\n if (!response.hasData) {\n return const Center(\n child: const Text(\'Loading Messages...\'),\n );\n } else if (response.data.statusCode != 200) {\n return const Center(\n child: const Text(\'Error loading data\'),\n );\n } else {\n List json = JSON.decode(response.data.body);\n messagelist = [];\n json.forEach((element) {\n DateTime submitdate = DateTime.parse(element[\'submitdate\']).toLocal();\n String myvideo = element[\'chatvideo\'];\n String myimage = element[\'chatimage\'];\n if (myimage != null) {\n imgStr = \'http://loop-dev.clinicalsoftworks.com/chat/getimage/\'+element[\'chatimage\'];\n } else if (myvideo != null) {\n vidStr = \'http://loop-dev.clinicalsoftworks.com/chatuploads/\'+element[\'chatvideo\'];\n vcontroller = new VideoPlayerController(vidStr)..initialize();\n }\n _showLgPic() {\n Route route = new MaterialPageRoute(\n settings: new RouteSettings(name: \"/ShowPic\"),\n builder: (BuildContext context) => new ShowPic(\n image: imgStr,\n ),\n );\n Navigator.of(context).push(route);\n }\n _addImage() {\n //assert(imgStr != null);\n //myimageview = new Image.memory(mbytes);\n new GestureDetector(\n /*child: new Image(\n image: newimageview.image,\n width: 300.0,\n ),*/\n child: new Image.network(imgStr),\n onTap: _showLgPic,\n );\n }\n _addNoImage() {\n assert(imgStr == null);\n new Text(\'\');\n }\n _showAssets() {\n if (imgStr != null) {\n new GestureDetector(\n child: new Image.network(\n imgStr,\n width: 300.0,\n ),\n onTap: _showLgPic,\n );\n } else if (vidStr != null) {\n new vplayer.VideoCard(controller: vcontroller,title: element[\'referralname\'],subtitle: \'video\',);\n } else {\n new Container();\n }\n }\n messagelist.add(\n new Container(\n //width: 300.0,\n padding: new EdgeInsets.all(10.0),\n child: new Column(\n mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n crossAxisAlignment: CrossAxisAlignment.stretch,\n mainAxisSize: MainAxisSize.min,\n children: [\n new Container(\n padding: new EdgeInsets.only(bottom: 5.0),\n child: new Row(\n mainAxisSize: MainAxisSize.min,\n children: [\n new CircleAvatar(\n child: new Text(\n element[\'sendname\'][0],\n style: new TextStyle(fontSize: 15.0),\n ),\n radius: 12.0,\n ),\n new Text(\' \'),\n new Text(\n element[\'sendname\'],\n style: new TextStyle(\n fontSize: 15.0, fontWeight: FontWeight.bold),\n ),\n new Text(\' \'),\n new Text(\n new DateFormat.Hm().format(submitdate),\n style: new TextStyle(color: Colors.grey, fontSize: 12.0),\n ),\n //new Text(submitdate.toLocal().toString())\n ],\n ),\n ),\n new Row(\n children: [\n new Text(\' \'),\n new Flexible(\n child: new Text(\'${element[\'message\']}\'),\n )\n ],\n ),\n new Container(\n width: 300.0,\n child: new Row(\n children: [\n new Text(\' \'),\n //_showAssets(),\n imgStr != null\n ? new GestureDetector(\n child: new Image.network(\n imgStr,\n width: 300.0,\n ),\n onTap: _showLgPic,\n )\n : vidStr != null\n ? new Flexible(\n child: new vplayer.VideoCard(\n controller: vcontroller,\n title: element[\'referralname\'],\n subtitle: \'video\',\n ),\n )\n : new Container(),\n ],\n )),\n ],\n ),\n ),\n );\n });\n return new Column(children: messagelist);\n }\n },\n ),\n ],\n );\n }\n}