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