先判断该消息是否时自己发的,如果是自己发的,列表就跳到底部,如果不是自己发的消息,就判断是否在底部,如果不在底部就显示“有未读消息”,如果在底部就不用显示“有未读消息”。点击“有未读消息”跳转到列表底部。因为列表反转了,所以底部是0。顶部是列表高度
late StreamSubscription<ReceiveNewMessageEvent> _receiveNewMessageEvent;
final ScrollController _scrollController = ScrollController();
//插入一条新消息,判断是否时自己的消息
handleMessage(int userId) {
if ('$userId' == Global.userId) {
//是自己发的消息
EventBusUtil.fire(ReceiveNewMessageEvent(CloudCustomDataBean.LIVE_MESSAGE, true));
} else {
//不是自己发的消息
EventBusUtil.fire(ReceiveNewMessageEvent(CloudCustomDataBean.LIVE_MESSAGE, false));
}
}
///新消息处理
_receiveNewMessageEvent = EventBusUtil.listen((event) {
if (event.messageType == CloudCustomDataBean.LIVE_MESSAGE) {
if (event.isOneself) {
//是自己的消息,弹到底部
_scrollController.animateTo(
_scrollController.position.minScrollExtent, //滚动到底部
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut);
} else {
//不是自己的消息,不在列表底部就判断是否显示未读消息控件
if (!isOnTheBottom) {
//显示有未读消息控件
isNewMessage = true;
mySetState(() {
});
}
}
}
});
/// 监听滚动事件,判断列表是否到达底部
_scrollController.addListener(() {
if (_scrollController.position.pixels <=
_scrollController.position.minScrollExtent + 30) {
//小于距离底部30位置的算滑到底部了
///达到最大指定滚动位置
isOnTheBottom = true;
isNewMessage = false;
} else {
//因为列表反转了,底下是0,顶部是列表最大高度,所以列表滑动超过距离底部30的位置时就会设置还没到列表底部(大概滑到距离底部两条消息的距离才会显示还有未读消息)
isOnTheBottom = false;
}
mySetState(() {
});
});
@override
void dispose() {
_receiveNewMessageEvent.cancel();
_scrollController.dispose();
super.dispose();
}
//列表数据
DirectSeedingList(
widget.roomId ?? '0',
liveGroupInfo,
_scrollController,
widget.anchorInfo),
//点击未读消息按钮跳到底部
Offstage(
offstage: !isNewMessage,
child: GestureDetector(
onTap: () {
_scrollController.animateTo(
_scrollController.position.minScrollExtent, //滚动到底部
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut);
},
child: Container(
margin: const EdgeInsets.only(bottom: 8,left: 15),
padding: const EdgeInsets.fromLTRB(11, 8, 11, 8),
decoration: BoxDecorationUtil()
.setFillBoxDecoration(Colors.white, 15.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ContentText(S.current.group_unread, 12.0,
const Color(0xFFE95D47),
fontWeight: FontWeight.w500),
RightPadding(4),
const Image(
image: AssetImage(
'assets/images/group/icon_group_new_message.png'),
width: 9,
height: 9,
gaplessPlayback: true,color: Color(0xFFE95D47),)
]))))
列表布局
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:social_im/common/globalEventBus.dart';
import 'package:social_im/models/cloudCustomDataBean.dart';
import 'package:social_im/models/group/groupInfoBean.dart';
import 'package:social_im/utils/loadingUtil.dart';
import 'package:social_im/utils/widgetAdaptation.dart';
import '../../../provider/direct_seeding_message_list_model.dart';
import '../direct_model/direct_anchor_info.dart';
import '../direct_seeding_message_model/direct_seeding_all_message_model.dart';
import 'direct_message_item.dart';
class DirectSeedingList extends StatefulWidget {
String groupId;
GroupInfoBean? liveGroupInfo;
ScrollController scrollController;
DirectAnchorInfoEnter? anchorInfo;
DirectSeedingList(
this.groupId, this.liveGroupInfo, this.scrollController, this.anchorInfo);
@override
State<StatefulWidget> createState() => DirectSeedingListState();
}
class DirectSeedingListState extends State<DirectSeedingList> {
/// 滚动控制器
final GlobalKey _containerKey = GlobalKey();
List<DirectSeedingAllMessageModel> groupMessageList = [];
double clmHeight = 0;
AudioPlayer audioPlayer = AudioPlayer();
late StreamSubscription<GetSelfRole>? _getSelfRole;
int userRole = 0;
@override
void initState() {
super.initState();
_getSelfRole = EventBusUtil.listen((event) {
userRole = event.userRole;
mySetState(() {
});
});
// receiveMessage();
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
}
@override
void dispose() {
if (_getSelfRole != null) {
_getSelfRole!.cancel();
}
LoadingUtil.dismissLoading();
// removeMessage();
super.dispose();
}
_afterLayout(_) {
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted) {
setState(() {
clmHeight = _containerKey.currentContext!.size!.height;
});
}
});
}
mySetState(callBack) {
if (mounted) {
setState(() {
callBack();
});
}
}
@override
Widget build(BuildContext context) {
groupMessageList =
Provider.of<DirectSeedingMessageListModel>(context, listen: true)
.directSeedingMessageList;
return ShaderMask(
//此处是背景透明度渐变的处理
shaderCallback: (Rect bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(0, 0, 0, 0),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 1),
],
).createShader(Rect.fromLTRB(0, 0, bounds.width, bounds.height));
},
blendMode: BlendMode.dstIn,
child: SingleChildScrollView(
controller: widget.scrollController,
scrollDirection: Axis.vertical,
key: _containerKey,
///注意设置为反向,
reverse: groupMessageList.length >= 2 ? true : false,
child: Column(children: [
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: groupMessageList.length,
itemBuilder: (BuildContext context, int index) {
return showItemView(groupMessageList[index]);
}))
])));
}
Widget showItemView(DirectSeedingAllMessageModel groupMessage) {
Widget groupMessageView = Container();
groupMessageView = Container(
margin: EdgeInsets.only(
left: WidgetAdaptation.getWidth(15),
right: WidgetAdaptation.getWidth(15)),
child: DirectMessageItem(
groupMessage,
audioPlayer,
activityType: CloudCustomDataBean.WORLD_LIVE_GROUP,
roomId: int.parse(widget.groupId),
anchorInfo: widget.anchorInfo,
userRole: userRole,
),
);
return groupMessageView;
}
}