flutter聊天界面-聊天列表 下拉加载更多历史消息

flutter聊天界面-聊天列表 下拉加载更多历史消息

在之前实现了flutter聊天界面的富文本展示内容、自定义表情键盘实现、加号【➕】更多展开相机、相册等操作Panel、消息气泡展示实现Flexible。这里把实现的聊天界面的滑动列表及下拉加载更多历史消息记录一下

聊天界面的列表使用ListView。

一、效果图

在这里插入图片描述

二、ListView

ListView是滚动组件,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)。

ListView({...  //可滚动widget公共参数Axis scrollDirection = Axis.vertical,bool reverse = false,ScrollController? controller,bool? primary,ScrollPhysics? physics,EdgeInsetsGeometry? padding,//ListView各个构造函数的共同参数  double? itemExtent,Widget? prototypeItem, //列表项原型,后面解释bool shrinkWrap = false,bool addAutomaticKeepAlives = true,bool addRepaintBoundaries = true,double? cacheExtent, // 预渲染区域长度//子widget列表List<Widget> children = const <Widget>[],
})

后续聊天界面会用到reverse、physics、controller等

三、聊天界面消息列表

聊天界面列表滚动使用的是ListView.builder。
需要设置shrinkWrap

shrinkWrap:该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。

reverse:设置reverse为ture,内容会倒过来显示。

3.1、聊天列表

// 聊天列表Widget buildScrollConfiguration(ChatContainerModel model, BuildContext context) {return ListView.builder(physics: AlwaysScrollableScrollPhysics(),key: chatListViewKey,shrinkWrap: true,addRepaintBoundaries: false,controller: scrollController,padding:const EdgeInsets.only(left: 0.0, right: 0.0, bottom: 0.0, top: 0.0),itemCount: messageList.length + 1,reverse: true,clipBehavior: Clip.none,itemBuilder: (BuildContext context, int index) {if (index == messageList.length) {if (historyMessageList != null && historyMessageList!.isEmpty) {return const ChatNoMoreIndicator();}return const ChatLoadingIndicator();} else {CommonChatMessage chatMessage = messageList[index];return ChatCellElem(childElem: MessageElemHelper.layoutCellElem(chatMessage),chatMessage: chatMessage,onSendFailedIndicatorPressed: (CommonChatMessage chatMessage) {onSendFailedIndicatorPressed(context, chatMessage);},onBubbleTapPressed: (CommonChatMessage chatMessage) {onBubbleTapPressed(context, chatMessage);},onBubbleDoubleTapPressed: (CommonChatMessage chatMessage) {onBubbleDoubleTapPressed(context, chatMessage);},onBubbleLongPressed: (CommonChatMessage chatMessage,LongPressStartDetails details,ChatBubbleFrame? chatBubbleFrame) {onBubbleLongPressed(context, chatMessage, details, chatBubbleFrame);},);}},);}

3.2、聊天界面条目较少时候,iOS滚动范围较小、无法回弹问题

这个问题,这里使用的是CustomScrollView来进行嵌套ListView。CustomScrollView 的主要功能是提供一个公共的 Scrollable 和 Viewport,来组合多个 Sliver。

具体实现代码

// 嵌套的customScrollViewWidget buildCustomScrollView(ChatContainerModel model, BuildContext context) {return LayoutBuilder(builder: (BuildContext lbContext, BoxConstraints constraints) {double layoutHeight = constraints.biggest.height;return CustomScrollView(slivers: <Widget>[SliverPadding(padding: EdgeInsets.all(0.0),sliver: SliverToBoxAdapter(child: Container(alignment: Alignment.topCenter,height: layoutHeight,child: buildScrollConfiguration(model, context),),),),],);});}

3.3、使用ListView的reverse为true时候,导致条目太少的时候会从下往上显示,导致顶部大片空白

导致条目太少的时候会从下往上显示,导致顶部大片空白的情况是由于界面及下面的表情键盘、输入框等使用的是Column控件。所以要用到Expanded来填充,Expanded组件强制子组件填充可用空间,Expanded会强制填充剩余留白空间。

Widget buildListContainer(ChatContainerModel model, BuildContext context) {return Expanded(child: Container(decoration: BoxDecoration(color: ColorUtil.hexColor(0xf7f7f7),),clipBehavior: Clip.hardEdge,alignment: Alignment.topCenter,child: isNeedDismissPanelGesture? GestureDetector(onPanDown: handlerGestureTapDown,child: buildCustomScrollView(model, context),): buildCustomScrollView(model, context),),);}

// 界面及下面的表情键盘、输入框等使用的是Column控件

return Container(key: chatContainerKey,width: double.infinity,height: double.infinity,child: Column(mainAxisAlignment: MainAxisAlignment.start,children: [buildChatStatisticsBar(model),ChatAnnouncementBar(announcementNotice: model.announcementNotice,onAnnouncementPressed: () {onAnnouncementPressed(model.announcementNotice);},),buildListContainer(model, context),ChatNavigatorBar(onNavigatorItemPressed: (CommNavigatorEntry navigatorEntry) {onNavigatorItemPressed(navigatorEntry, model);},navigatorEntries: model.navigatorEntries,),ChatInputBar(chatInputBarController: chatInputBarController,moreOptionEntries: model.moreOptionEntries,showPostEnterButton: checkShowPostAndStatistics(model),),],),);

3.4、列表滑动弹性效果

需要自定义ChatScrollPhysics,该类继承ScrollPhysics

实现下滑加载带弹性效果,上滑屏蔽弹性效果。(BouncingScrollPhysics是上下都有弹性效果)

class ChatScrollPhysics extends ScrollPhysics {/// Creates scroll physics that bounce back from the edge.const ChatScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);ChatScrollPhysics applyTo(ScrollPhysics? ancestor) {return ChatScrollPhysics(parent: buildParent(ancestor));}/// The multiple applied to overscroll to make it appear that scrolling past/// the edge of the scrollable contents is harder than scrolling the list./// This is done by reducing the ratio of the scroll effect output vs the/// scroll gesture input.////// This factor starts at 0.52 and progressively becomes harder to overscroll/// as more of the area past the edge is dragged in (represented by an increasing/// `overscrollFraction` which starts at 0 when there is no overscroll).double frictionFactor(double overscrollFraction) =>0.52 * math.pow(1 - overscrollFraction, 2);double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {print("applyPhysicsToUserOffset position:${position}, offset:${offset}");assert(offset != 0.0);assert(position.minScrollExtent <= position.maxScrollExtent);if (!position.outOfRange) return offset;final double overscrollPastStart =math.max(position.minScrollExtent - position.pixels, 0.0);final double overscrollPastEnd =math.max(position.pixels - position.maxScrollExtent, 0.0);final double overscrollPast =math.max(overscrollPastStart, overscrollPastEnd);final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) ||(overscrollPastEnd > 0.0 && offset > 0.0);final double friction = easing// Apply less resistance when easing the overscroll vs tensioning.? frictionFactor((overscrollPast - offset.abs()) / position.viewportDimension): frictionFactor(overscrollPast / position.viewportDimension);final double direction = offset.sign;double applyPhysicsToUserOffset =direction * _applyFriction(overscrollPast, offset.abs(), friction);print("applyPhysicsToUserOffset:${applyPhysicsToUserOffset}");return applyPhysicsToUserOffset;}static double _applyFriction(double extentOutside, double absDelta, double gamma) {assert(absDelta > 0);double total = 0.0;if (extentOutside > 0) {final double deltaToLimit = extentOutside / gamma;if (absDelta < deltaToLimit) return absDelta * gamma;total += extentOutside;absDelta -= deltaToLimit;}return total + absDelta;}double applyBoundaryConditions(ScrollMetrics position, double value) {print("applyBoundaryConditions:${position},value:${value}");return 0.0;}Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {final Tolerance tolerance = this.tolerance;print("createBallisticSimulation:${position},velocity:${velocity},tolerance.velocity:${tolerance.velocity}");if (velocity.abs() >= tolerance.velocity || position.outOfRange) {return BouncingScrollSimulation(spring: spring,position: position.pixels,velocity: velocity,leadingExtent: position.minScrollExtent,trailingExtent: position.maxScrollExtent,tolerance: tolerance,);}return null;}// The ballistic simulation here decelerates more slowly than the one for// ClampingScrollPhysics so we require a more deliberate input gesture// to trigger a fling.double get minFlingVelocity {double aMinFlingVelocity = kMinFlingVelocity * 2.0;print("minFlingVelocity:${aMinFlingVelocity}");return aMinFlingVelocity;}// Methodology:// 1- Use https://github.com/flutter/platform_tests/tree/master/scroll_overlay to test with//    Flutter and platform scroll views superimposed.// 3- If the scrollables stopped overlapping at any moment, adjust the desired//    output value of this function at that input speed.// 4- Feed new input/output set into a power curve fitter. Change function//    and repeat from 2.// 5- Repeat from 2 with medium and slow flings./// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings.////// The velocity of the last fling is not an important factor. Existing speed/// and (related) time since last fling are factors for the velocity transfer/// calculations.double carriedMomentum(double existingVelocity) {double aCarriedMomentum = existingVelocity.sign *math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(),40000.0);print("carriedMomentum:${aCarriedMomentum},existingVelocity:${existingVelocity}");return aCarriedMomentum;}// Eyeballed from observation to counter the effect of an unintended scroll// from the natural motion of lifting the finger after a scroll.double get dragStartDistanceMotionThreshold {print("dragStartDistanceMotionThreshold");return 3.5;}
}

3.5、去除ListView滑动波纹 - 定义ScrollBehavior

实现ScrollBehavior

class ChatScrollBehavior extends ScrollBehavior {final bool showLeading;final bool showTrailing;ChatScrollBehavior({this.showLeading: false,	//不显示头部水波纹this.showTrailing: false,	//不显示尾部水波纹});Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {switch (getPlatform(context)) {case TargetPlatform.iOS:return child;case TargetPlatform.android:case TargetPlatform.fuchsia:return GlowingOverscrollIndicator(child: child,showLeading: showLeading,showTrailing: showTrailing,axisDirection: axisDirection,color: Theme.of(context).accentColor,);}return null;}
}

四、下拉加载更多消息与没有更多消息

在下拉加载更多消息时,在listview上加ChatLoadingIndicator

在列表的最后一条进行判断。列表的

itemCount: messageList.length + 1,
if (index == messageList.length) {if (historyMessageList != null && historyMessageList!.isEmpty) {return const ChatNoMoreIndicator();}return const ChatLoadingIndicator();}

加载更多消息Indicator代码

// 刷新的动画
class ChatLoadingIndicator extends StatelessWidget {const ChatLoadingIndicator({Key? key}) : super(key: key);Widget build(BuildContext context) {return Container(height: 60.0,width: double.infinity,alignment: Alignment.center,child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: [CupertinoActivityIndicator(color: ColorUtil.hexColor(0x333333),),const SizedBox(width: 10,),buildIndicatorTitle(context),],),);}Widget buildIndicatorTitle(BuildContext context) {return Text("加载中",textAlign: TextAlign.left,maxLines: 1000,overflow: TextOverflow.ellipsis,softWrap: true,style: TextStyle(fontSize: 14,fontWeight: FontWeight.w500,fontStyle: FontStyle.normal,color: ColorUtil.hexColor(0x555555),decoration: TextDecoration.none,),);}
}

当没有更多数据的时候,这时候需要显示没有更多消息了。

// 没有更多消息时候
class ChatNoMoreIndicator extends StatelessWidget {const ChatNoMoreIndicator({Key? key}) : super(key: key);Widget build(BuildContext context) {return Container(height: 40.0,width: double.infinity,alignment: Alignment.center,// 不显示提示文本child: buildIndicatorTitle(context),);}Widget buildIndicatorTitle(BuildContext context) {return Text("没有更多消息",textAlign: TextAlign.left,maxLines: 1,overflow: TextOverflow.ellipsis,softWrap: true,style: TextStyle(fontSize: 14,fontWeight: FontWeight.w500,fontStyle: FontStyle.normal,color: ColorUtil.hexColor(0x555555),decoration: TextDecoration.none,),);}
}

监听ScorllController来控制加载等多消息

判断scrollController.position.pixels与scrollController.position.maxScrollExtent

// 滚动控制器Controllervoid addScrollListener() {scrollController.addListener(() {LoggerManager().debug("addScrollListener pixels:${scrollController.position.pixels},""maxScrollExtent:${scrollController.position.maxScrollExtent}""isLoading:${isLoading}");if (scrollController.position.pixels >=scrollController.position.maxScrollExtent) {if (isLoading == false) {loadHistoryMore();}}});}

至此flutter聊天界面-聊天列表 下拉加载更多历史消息基本完成,这里有很多封装的消息类。后续的发送消息的操作等再整理。

五、小结

flutter聊天界面-聊天列表 下拉加载更多历史消息,主要实现Column中使用Expand嵌套ListView布局,设置reverse、physics、ScrollBehavior。可以解决reverse为true首导致顶部大片空白问题,去除ListView滑动波纹。之后在消息的最后一条设置为加载更多消息指示器与没有更多消息提示。

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/13793.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python与深度学习——基础环境搭建

一、安装jupyter notebook Jupyter Notebook是一个开源的交互式笔记本环境&#xff0c;可以用于编写和执行代码、创建可视化效果、展示数据分析结果等。我们在这里用它实现代码运行和观察运行结果。安装jupyter notebook实质上是安装Anaconda,后续还要在Anaconda Prompt中使用c…

QTday2

第一个界面头部的代码 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include<QLabel> #include<QIcon> #include<QLineEdit> #include<QDebug> class Widget : public QWidget {Q_OBJECTsignals:void j…

Unity:sentinel key not found (h0007)

SSD换电脑&#xff0c;unity 编辑器无法打开&#xff1b; 具体步骤&#xff1a; 删除这个路径下的文件 C:\ProgramData\SafeNet 下 Sentinel LDK 打开Windows 的Cmd 命令行&#xff0c;输入编辑器版本下Unity.exe的路径&#xff0c; CD E:\Dev_Env\Unity\Hub\Editor\2020.3.3…

Java Excel 打开文件报发现“xx.xlsx”中的部分内容有问题。是否让我们尽量尝试恢复问题解决

问题描述&#xff1a; 发现“文件.xlsx”中的部分内容有问题。是否让我们尽量尝试恢复&#xff1f; 问题分析&#xff1a; 1、后端的导出接口写的不对&#xff0c;又返回流数据&#xff0c;又返回响应体数据&#xff0c;导致前端将流数据和响应体数据都下载到了excel文件中。…

leetcode 106. 从中序与后序遍历序列构造二叉树

2023.7.8 让我很难受的一道题&#xff0c;个人感觉难度不止中等。 首先要知道的是知道了前序/后序 中序 之后&#xff0c;是可以构造出相应且唯一的二叉树的。 本道题的思路通过递归的方式根据中序遍历数组和后序遍历数组构建二叉树&#xff0c;并返回根节点。递归的结束条…

项目中期检查会议和进度对接

1.召开中期项目检查会议&#xff0c;与团队成员和博士王锟对接进度。对整体项目表示满意接受&#xff0c;指出重点需要修改提升和进一步开发完善的部分&#xff0c;以增强系统的完整度、功能亮点和界面数量点。具体为 ①注重“highlight”&#xff0c;即布局凸显主题功能&…

ubuntu常用软件安装、异常处理

1.ubuntu更换源 打开以下文件&#xff1a; sudo gedit /etc/apt/sources.list 在文件中添加如下内容 #中科大源 deb https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main re…

大学英语六级相当于雅思考试多少分

雅思考试的难度&#xff0c;可以和大学英语六级进行对应&#xff0c;大家可以通过分数来基本确认雅思考试的难度系数。跟着小编来一起看看大学英语六级相当于雅思考试多少分&#xff1f; 英语六级相当于雅思多少分 大学英语六级和雅思没有直接的分数对应关系&#xff0c;一般大…

Django学习笔记

Django学习笔记 初识Django安装Django创建Django项目APP启动Django快速上手再写一个页面templates模板静态文件 模板语法请求和相应登录案例 数据库操作安装第三方模块ORM Django官网 : https://docs.djangoproject.com/en/4.2/Django中文文档参考网站&#xff1a;https://yiyi…

XSS学习

目录 什么是XSS 概念 理解 XSS分类 存储型XSS 反射型XSS 原理 攻击过程 DOM型 攻击过程 DOM行XSS与反射型XSS区别 存储型XSS与反射型XSS区别 DVWA实验 反射型XSS low等级 JavaScript弹窗函数 攻击思路 攻击者web设计 medium等级 high等级 impissible等级 …

【openGauss数据库】--运维指南04--数据导入

【openGauss数据库】--运维指南04--数据导入 &#x1f53b; 一、openGauss导入数据&#x1f530; 1.1 概述&#x1f530; 1.2 INSERT语句写入数据&#x1f530; 1.3 gsql元命令导入数据&#x1f530; 1.4 使用gs_restore命令、gsql命令导入数据&#xff08;主要&#xff09; &a…

【计算机视觉】对比学习采样器sampler

前置知识准备 Samplers — Open Metric Learning documentation​​​​​​ 在该文档里&#xff0c;category表示类别&#xff0c;label表示商品&#xff0c;instance表示商品不同角度的图片。 category就是blouses_shirts&#xff1b;label就是15&#xff1b;instance就是这…