Flutter滚动到动态ListView的底部

11 浏览
0 Comments

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}

0
0 Comments

问题:Flutter动态ListView如何滚动到底部?

原因:动态ListView的数据会随着用户的操作而不断更新,因此需要在ListView中添加滚动控制器,以便在数据更新后将其滚动到底部。

解决方法:首先创建一个ScrollController对象,并将其赋值给ListView的controller属性。然后使用ListView.builder构造方法创建动态ListView,其中itemCount为数据列表的长度,itemBuilder为每个列表项的构建方法。最后,调用_scrollController.animateTo方法,将ListView滚动到最大滚动位置,实现滚动到底部的效果。

具体代码如下:

import 'package:flutter/material.dart';

ScrollController _scrollController = new ScrollController();

ListView.builder(

controller: _scrollController,

itemCount: list.length,

itemBuilder: (BuildContext ctxt, int index) {

return Text("GMF ${list[index]}");

}

)

_scrollController.animateTo(

_scrollController.position.maxScrollExtent,

duration: const Duration(milliseconds: 500),

curve: Curves.easeOut,

);

通过以上步骤,我们可以实现在动态ListView中滚动到底部的效果。

0
0 Comments

问题原因:动态列表视图在初始状态下无法自动滚动到底部。

解决方法:在ListView中添加reverse: trueshrinkWrap: true属性,并在列表中使用listModel = List.from(listModel.reversed);将列表翻转。

这个解决方法对于聊天列表非常有用,它能自动滚动到底部。另一种使用控制器跳转到底部的选项不够平滑,并且不允许用户向上滚动到顶部而不跳转到底部。

0
0 Comments

问题的出现原因:在动态列表视图中,想要将滚动条自动滚动到底部,但是使用了reverse:true属性后,列表内容会倒序显示。

解决方法:为了实现自动滚动到底部的效果,可以通过设置reverse:true属性以及使用ScrollController来实现。首先,在ListView中设置reverse:true属性,并使用controller来控制滚动位置,接着通过_scrollController.animateTo方法将滚动位置设置为0.0即可。具体代码如下:

ListView(

shrinkWrap: true,

controller: _scrollController,

reverse: true, // 设置为true

children: _getChildren(),

);

然后,通过以下代码定义_scrollController

ScrollController _scrollController = new ScrollController();

需要注意的是,使用reverse:true属性后,列表内容会倒序显示,这可能会导致与键(Keys)的使用不兼容,因为渲染树和实际UI是倒序的。对于静态内容来说这已经足够了,但是对于动态改变的有状态(stateful)的值,这可能会导致问题。这就是为什么reverse:true的含义。

0