Touch position are incorrect
Closed this issue · 2 comments
HuangZiquan commented
Hi,
The touch position of the item is incorrect,when items not out of page.
edit in your chat example :
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter_list_view/flutter_list_view.dart';
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
enum MessageType {
sent,
receive,
tag,
}
class ChatModel {
ChatModel({required this.id, required this.msg, required this.type});
int id;
String msg;
MessageType type;
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
@override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
int currentId = 0;
List<ChatModel> messages = [];
final myController = TextEditingController();
final listViewController = FlutterListViewController();
final refreshController = RefreshController(initialRefresh: false);
/// Using init index to control load first messages
int initIndex = 0;
double initOffset = 0.0;
bool initOffsetBasedOnBottom = true;
int forceToExecuteInitIndex = 0;
// Fire refresh temp variable
double prevScrollOffset = 0;
List<FlutterListViewItemPosition>? itemPositions;
double listviewHeight = 0;
@override
void initState() {
_loadMessages();
listViewController.addListener(() {
const torrentDistance = 40;
var offset = listViewController.offset;
if (offset <= torrentDistance && prevScrollOffset > torrentDistance) {
if (!refreshController.isRefresh) {
refreshController.requestRefresh();
}
}
prevScrollOffset = offset;
});
listViewController.sliverController.onPaintItemPositionsCallback = (widgetHeight, positions) {
itemPositions = positions;
listviewHeight = widgetHeight;
};
super.initState();
}
/// It is mockup to load messages from server
_loadMessages() async {
await Future.delayed(const Duration(milliseconds: 100));
var prevTimes = Random().nextInt(20) + 1;
// for (var i = 0; i < prevTimes; i++) {
// _insertReceiveMessage("The demo also show how to reverse a list in\r\n" *
// (Random().nextInt(4) + 1));
// }
_insertTagMessage("Last readed");
var nextTimes = Random().nextInt(20) + 1;
// for (var i = 0; i < nextTimes; i++) {
// _insertReceiveMessage("The demo also show how to reverse a list in\r\n" *
// (Random().nextInt(4) + 1));
// }
_insertSendMessage("If message more than two screens and scroll over 80px, the scroll not move if a message coming or you input a message");
_insertSendMessage("It resoved the problem which is when you read a message while a lot of messages coming");
_insertSendMessage("You can't focus the message content what you read");
_insertSendMessage("The demo also show how to reverse a list in the controll");
_insertSendMessage("When reverse the list, the item still show on top of list if the messages didn't fill full screen");
initIndex = messages.length - prevTimes - 1;
print("--------------------$initIndex");
setState(() {});
}
_insertSendMessage(String msg, {bool appendToTailer = false}) {
if (appendToTailer) {
messages.add(ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.sent));
} else {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.sent));
}
}
_insertReceiveMessage(String msg, {bool appendToTailer = false}) {
if (appendToTailer) {
messages.add(ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.receive));
} else {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.receive));
}
}
_insertTagMessage(String msg) {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.tag));
}
_mockToReceiveMessage() {
var times = Random().nextInt(4) + 1;
for (var i = 0; i < times; i++) {
_insertReceiveMessage("The demo also show how to reverse a list in\r\n" * (Random().nextInt(4) + 1));
}
setState(() {});
}
_sendMessage() {
if (myController.text.isNotEmpty) {
if (messages.isNotEmpty) {
listViewController.sliverController.jumpToIndex(0);
}
setState(() {
_insertSendMessage(myController.text);
});
myController.text = "";
}
}
void _onRefresh() async {
print("------------------------------------_onRefresh");
await Future.delayed(const Duration(milliseconds: 2000));
var newMessgeLength = 20;
for (var i = 0; i < newMessgeLength; i++) {
_insertReceiveMessage("The demo also show how to reverse a list in\r\n" * (Random().nextInt(4) + 1));
}
var firstIndex = newMessgeLength;
var firstOffset = 0.0;
if (itemPositions != null && itemPositions!.isNotEmpty) {
var firstItemPosition = itemPositions![0];
firstIndex = firstItemPosition.index + newMessgeLength;
firstOffset = listviewHeight - firstItemPosition.offset - firstItemPosition.height;
}
initIndex = firstIndex;
initOffsetBasedOnBottom = false;
forceToExecuteInitIndex++;
initOffset = firstOffset;
refreshController.refreshCompleted();
setState(() {});
}
void _onLoading() async {
await Future.delayed(const Duration(milliseconds: 1000));
for (var i = 0; i < 50; i++) {
_insertReceiveMessage("The demo also show how to append message\r\n" * (Random().nextInt(4) + 1), appendToTailer: true);
}
if (mounted) setState(() {});
refreshController.loadComplete();
}
_renderItem(int index) {
var msg = messages[index];
if (msg.type == MessageType.tag) {
return Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: const BoxDecoration(color: Colors.grey, borderRadius: BorderRadius.all(Radius.circular(5))),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
msg.msg,
style: const TextStyle(fontSize: 14.0, color: Colors.white),
),
),
),
),
);
} else {
return Align(
alignment: msg.type == MessageType.sent ? Alignment.centerRight : Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () {
print(msg.msg);
},
child: Container(
decoration: BoxDecoration(
color: msg.type == MessageType.sent ? Colors.blue : Colors.green,
borderRadius: msg.type == MessageType.sent
? const BorderRadius.only(topLeft: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20))
: const BorderRadius.only(topRight: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20))),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
msg.msg,
style: const TextStyle(fontSize: 14.0, color: Colors.white),
),
),
),
),
),
);
}
}
_renderList() {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
}),
child: SmartRefresher(
enablePullDown: false,
enablePullUp: false,
header: CustomHeader(
completeDuration: const Duration(milliseconds: 0),
builder: (context, mode) {
Widget body;
if (mode == RefreshStatus.idle) {
body = const Text("Pull up load prev msg");
} else if (mode == RefreshStatus.refreshing) {
body = const CupertinoActivityIndicator();
} else if (mode == RefreshStatus.failed) {
body = const Text("Load Failed!Click retry!");
} else if (mode == RefreshStatus.canRefresh) {
body = const Text("Release to load more");
} else {
body = const Text("No more Data");
}
if (mode == RefreshStatus.completed) {
return Container();
} else {
return RotatedBox(
quarterTurns: 2,
child: SizedBox(
height: 55.0,
child: Center(child: body),
),
);
}
},
),
// const WaterDropHeader(),
footer: CustomFooter(
builder: (context, mode) {
Widget body;
if (mode == LoadStatus.idle) {
body = const Text("Pull down to load more message");
} else if (mode == LoadStatus.loading) {
body = const CupertinoActivityIndicator();
} else if (mode == LoadStatus.failed) {
body = const Text("Load Failed!Click retry!");
} else if (mode == LoadStatus.canLoading) {
body = const Text("Release to load more");
} else {
body = const Text("No more Data");
}
return SizedBox(
height: 55.0,
child: Center(child: body),
);
},
),
controller: refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: FlutterListView(
reverse: true,
controller: listViewController,
delegate: FlutterListViewDelegate((BuildContext context, int index) => _renderItem(index),
childCount: messages.length,
onItemKey: (index) => messages[index].id.toString(),
keepPosition: true,
keepPositionOffset: 40,
initIndex: initIndex,
initOffset: initOffset,
initOffsetBasedOnBottom: initOffsetBasedOnBottom,
forceToExecuteInitIndex: forceToExecuteInitIndex,
firstItemAlign: FirstItemAlign.end))),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Chat"),
actions: [
TextButton(
onPressed: _mockToReceiveMessage,
child: const Text(
"Mock To Receive",
style: TextStyle(color: Colors.white),
))
],
),
resizeToAvoidBottomInset: true,
body: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
behavior: HitTestBehavior.opaque,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(flex: 1, child: _renderList()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Row(children: [
Expanded(
child: TextField(
controller: myController,
),
),
ElevatedButton(onPressed: _sendMessage, child: const Text("Send"))
]),
)
],
),
),
)));
}
@override
void dispose() {
myController.dispose();
listViewController.dispose();
refreshController.dispose();
super.dispose();
}
}
robert-luoqing commented
@HuangZiquan Thanks for your feedback, the issue have been fixed in flutter_list_view: ^1.1.16. Please update it.
The issue caused by firstItemAlign to FirstItemAlign.end
Thanks.
HuangZiquan commented
In the same example, I added a GlobalKey to the item, and I found that the global coordinate was incorrect.
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter_list_view/flutter_list_view.dart';
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
enum MessageType {
sent,
receive,
tag,
}
class ChatModel {
ChatModel({required this.id, required this.msg, required this.type});
int id;
String msg;
MessageType type;
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
@override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
int currentId = 0;
List<ChatModel> messages = [];
final myController = TextEditingController();
final listViewController = FlutterListViewController();
final refreshController = RefreshController(initialRefresh: false);
/// Using init index to control load first messages
int initIndex = 0;
double initOffset = 0.0;
bool initOffsetBasedOnBottom = true;
int forceToExecuteInitIndex = 0;
// Fire refresh temp variable
double prevScrollOffset = 0;
List<FlutterListViewItemPosition>? itemPositions;
double listviewHeight = 0;
@override
void initState() {
_loadMessages();
listViewController.addListener(() {
const torrentDistance = 40;
var offset = listViewController.offset;
if (offset <= torrentDistance && prevScrollOffset > torrentDistance) {
if (!refreshController.isRefresh) {
refreshController.requestRefresh();
}
}
prevScrollOffset = offset;
});
listViewController.sliverController.onPaintItemPositionsCallback = (widgetHeight, positions) {
itemPositions = positions;
listviewHeight = widgetHeight;
};
super.initState();
}
/// It is mockup to load messages from server
_loadMessages() async {
await Future.delayed(const Duration(milliseconds: 100));
var prevTimes = Random().nextInt(20) + 1;
// for (var i = 0; i < prevTimes; i++) {
// _insertReceiveMessage("The demo also show how to reverse a list in\r\n" *
// (Random().nextInt(4) + 1));
// }
_insertTagMessage("Last readed");
var nextTimes = Random().nextInt(20) + 1;
// for (var i = 0; i < nextTimes; i++) {
// _insertReceiveMessage("The demo also show how to reverse a list in\r\n" *
// (Random().nextInt(4) + 1));
// }
_insertSendMessage("If message more than two screens and scroll over 80px, the scroll not move if a message coming or you input a message");
_insertSendMessage("It resoved the problem which is when you read a message while a lot of messages coming");
_insertSendMessage("You can't focus the message content what you read");
_insertSendMessage("The demo also show how to reverse a list in the controll");
_insertSendMessage("When reverse the list, the item still show on top of list if the messages didn't fill full screen");
initIndex = messages.length - prevTimes - 1;
print("--------------------$initIndex");
setState(() {});
}
_insertSendMessage(String msg, {bool appendToTailer = false}) {
if (appendToTailer) {
messages.add(ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.sent));
} else {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.sent));
}
}
_insertReceiveMessage(String msg, {bool appendToTailer = false}) {
if (appendToTailer) {
messages.add(ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.receive));
} else {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.receive));
}
}
_insertTagMessage(String msg) {
messages.insert(0, ChatModel(id: ++currentId, msg: msg.trim(), type: MessageType.tag));
}
_mockToReceiveMessage() {
var times = Random().nextInt(4) + 1;
for (var i = 0; i < times; i++) {
_insertReceiveMessage("The demo also show how to reverse a list in\r\n" * (Random().nextInt(4) + 1));
}
setState(() {});
}
_sendMessage() {
if (myController.text.isNotEmpty) {
if (messages.isNotEmpty) {
listViewController.sliverController.jumpToIndex(0);
}
setState(() {
_insertSendMessage(myController.text);
});
myController.text = "";
}
}
void _onRefresh() async {
print("------------------------------------_onRefresh");
await Future.delayed(const Duration(milliseconds: 2000));
var newMessgeLength = 20;
for (var i = 0; i < newMessgeLength; i++) {
_insertReceiveMessage("The demo also show how to reverse a list in\r\n" * (Random().nextInt(4) + 1));
}
var firstIndex = newMessgeLength;
var firstOffset = 0.0;
if (itemPositions != null && itemPositions!.isNotEmpty) {
var firstItemPosition = itemPositions![0];
firstIndex = firstItemPosition.index + newMessgeLength;
firstOffset = listviewHeight - firstItemPosition.offset - firstItemPosition.height;
}
initIndex = firstIndex;
initOffsetBasedOnBottom = false;
forceToExecuteInitIndex++;
initOffset = firstOffset;
refreshController.refreshCompleted();
setState(() {});
}
void _onLoading() async {
await Future.delayed(const Duration(milliseconds: 1000));
for (var i = 0; i < 50; i++) {
_insertReceiveMessage("The demo also show how to append message\r\n" * (Random().nextInt(4) + 1), appendToTailer: true);
}
if (mounted) setState(() {});
refreshController.loadComplete();
}
_renderItem(int index) {
var msg = messages[index];
if (msg.type == MessageType.tag) {
return Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: const BoxDecoration(color: Colors.grey, borderRadius: BorderRadius.all(Radius.circular(5))),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
msg.msg,
style: const TextStyle(fontSize: 14.0, color: Colors.white),
),
),
),
),
);
} else {
final itemKey = GlobalKey();
return Align(
alignment: msg.type == MessageType.sent ? Alignment.centerRight : Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () {
print(msg.msg);
},
onLongPress: () {
final itemRenderBox = itemKey.currentContext!.findRenderObject() as RenderBox;
print(itemRenderBox.localToGlobal(Offset.zero));
},
child: Container(
key: itemKey,
decoration: BoxDecoration(
color: msg.type == MessageType.sent ? Colors.blue : Colors.green,
borderRadius: msg.type == MessageType.sent
? const BorderRadius.only(topLeft: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20))
: const BorderRadius.only(topRight: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20))),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
msg.msg,
style: const TextStyle(fontSize: 14.0, color: Colors.white),
),
),
),
),
),
);
}
}
_renderList() {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
}),
child: SmartRefresher(
enablePullDown: false,
enablePullUp: false,
header: CustomHeader(
completeDuration: const Duration(milliseconds: 0),
builder: (context, mode) {
Widget body;
if (mode == RefreshStatus.idle) {
body = const Text("Pull up load prev msg");
} else if (mode == RefreshStatus.refreshing) {
body = const CupertinoActivityIndicator();
} else if (mode == RefreshStatus.failed) {
body = const Text("Load Failed!Click retry!");
} else if (mode == RefreshStatus.canRefresh) {
body = const Text("Release to load more");
} else {
body = const Text("No more Data");
}
if (mode == RefreshStatus.completed) {
return Container();
} else {
return RotatedBox(
quarterTurns: 2,
child: SizedBox(
height: 55.0,
child: Center(child: body),
),
);
}
},
),
// const WaterDropHeader(),
footer: CustomFooter(
builder: (context, mode) {
Widget body;
if (mode == LoadStatus.idle) {
body = const Text("Pull down to load more message");
} else if (mode == LoadStatus.loading) {
body = const CupertinoActivityIndicator();
} else if (mode == LoadStatus.failed) {
body = const Text("Load Failed!Click retry!");
} else if (mode == LoadStatus.canLoading) {
body = const Text("Release to load more");
} else {
body = const Text("No more Data");
}
return SizedBox(
height: 55.0,
child: Center(child: body),
);
},
),
controller: refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: FlutterListView(
reverse: true,
controller: listViewController,
delegate: FlutterListViewDelegate((BuildContext context, int index) => _renderItem(index),
childCount: messages.length,
onItemKey: (index) => messages[index].id.toString(),
keepPosition: true,
keepPositionOffset: 40,
initIndex: initIndex,
initOffset: initOffset,
initOffsetBasedOnBottom: initOffsetBasedOnBottom,
forceToExecuteInitIndex: forceToExecuteInitIndex,
firstItemAlign: FirstItemAlign.end))),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Chat"),
actions: [
TextButton(
onPressed: _mockToReceiveMessage,
child: const Text(
"Mock To Receive",
style: TextStyle(color: Colors.white),
))
],
),
resizeToAvoidBottomInset: true,
body: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
behavior: HitTestBehavior.opaque,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(flex: 1, child: _renderList()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Row(children: [
Expanded(
child: TextField(
controller: myController,
),
),
ElevatedButton(onPressed: _sendMessage, child: const Text("Send"))
]),
)
],
),
),
)));
}
@override
void dispose() {
myController.dispose();
listViewController.dispose();
refreshController.dispose();
super.dispose();
}
}