Flutter - 聊天面板库动画生硬?这次让你丝滑个够
# 一、概述
距离以上两篇文章的发布已经快 1 年了, 当时的最新版本是 0.2.0
,现在,chat_bottom_container
迎来了 0.4.0
版本。
这次的更新主要是带来了自定义底部容器的功能,即容器的结构可完全由外部去定义,那可以通过这个功能去做什么呢?
- 保持面板视图的状态
- 实现任意动画(这次谁再说动画生硬,那就是你自己的问题了 😒)
# 二、效果
按照惯例,这里先附上效果图,好让大家直观感受一下~
例子 | 效果图 |
---|---|
Fade | ![]() |
Cube | ![]() |
Concentric | ![]() |
Rotation | ![]() |
ZoomIn | ![]() |
好了,更多效果大家可以跑示例去看看,下面我们一起来看看怎么使用吧。
# 三、集成
将 chat_bottom_container
添加到你的 pubspec.yaml
文件中
dependencies:
chat_bottom_container: ^0.4.0
最新版本可到 pub.dev
上复制粘贴 https://pub.dev/packages/chat_bottom_container (opens new window)
Android
端需要添加 jitpack
仓库到你的项目根目录下的 build.gradle
文件中:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
注:无论是
iOS
还是Android
,在下载依赖时都需要使用魔法❗️❗️❗️ 特别是iOS
,下载失败了,请先考虑是自己的节点问题,而不是别人的问题 🫵
# 四、使用
页面结构如下
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(),
body: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 列表
Expanded(child: ChatAnimationListView()),
// 功能条(输入框、表情按钮、工具按钮等)
ChatAnimationPanelBar(),
// 底部容器
ChatAnimationPanelContainer(),
],
),
);
这里主要看底部容器 ChatAnimationPanelContainer
对应的面板类型枚举
enum ChatAnimationPanelType {
none,
keyboard,
emoji,
tool,
}
底部容器的实现
ChatBottomPanelContainer<ChatAnimationPanelType>(
// 控制器(必传,ChatBottomPanelContainerController)
controller: state.controller,
// 输入框焦点对象(必传,FocusNode)
inputFocusNode: state.inputFocusNode,
// 面板类型切换回调(按需实现,主要用来记录当前的面板类型是什么)
onPanelTypeChange: logic.onPanelTypeChange,
// 自定义容器回调
customPanelContainer: (panelType, data) {
if (!mounted) return const SizedBox.shrink();
Widget? container = state.customPanelContainer;
if (container != null) {
// 已创建,刷新自定义容器,可以按需只刷新自定义容器里的子部件
logic.update([
ChatAnimationUpdateType.customPanelContainer,
]);
return container;
}
// 未创建,则创建
container = const ChatAnimationFadePanelContainer();
// 记录起来
state.customPanelContainer = container;
return container;
},
);
这里以上述效果图中的 Fade
为例,即 ChatAnimationFadePanelContainer
,主要是通过 animated_size_and_fade
这个第三方库去实现切换动画。
// ChatAnimationFadePanelContainer
Widget build(BuildContext context) {
return GetBuilder<ChatAnimationLogic>(
tag: logicTag,
id: ChatAnimationUpdateType.customPanelContainer,
builder: (_) {
if (!mounted) return const SizedBox.shrink();
// 构建对应类型的视图
// 如:常规安全区、键盘占位视图、表情面板、功能面板等
Widget resultWidget = logic.buildPanelWidget(
state.currentPanelType,
useKeyboardHeight: false,
);
// 包裹 AnimatedSizeAndFade 实现切换动画
resultWidget = AnimatedSizeAndFade(
fadeDuration: const Duration(milliseconds: 500),
sizeDuration: const Duration(milliseconds: 300),
child: resultWidget,
);
return resultWidget;
},
);
}
至此,通过 customPanelContainer
实现自定义容器的大体用法已经展示完毕了~
不过,相信聪明的你肯定会问,难道要完全自定义?能不能保留部分视图由 chat_bottom_container
帮我实现呢?比如常规安全区、键盘占位视图。
当然可以,接下来讲讲几点细节。
# 五、细节
# 构建内置面板
enum ChatBottomPanelType {
// 常规安全区
none,
// 键盘占位视图
keyboard,
// 其它面板(Emoji、Tool)
other,
}
Widget? buildInPanel(ChatBottomPanelType panelType) {
switch (panelType) {
case ChatBottomPanelType.none:
return _state?.buildSafeArea.call();
case ChatBottomPanelType.keyboard:
return _state?.buildKeyboardPlaceholderPanel.call();
case ChatBottomPanelType.other:
return _state?.buildOtherPanel.call();
}
}
通过调用 ChatBottomPanelContainerController
的 buildInPanel
方法,并传入 .none
或 .keyboard
时即可获取到相应的内置面板视图,而 .other
是获取外部 otherPanelWidget
回调返回的视图,未实现则得到 null
。
以下是构建对应类型的视图的示例代码
Widget buildPanelWidget(
ChatAnimationPanelType type,
...
) {
final panelController = state.controller;
...
Widget resultWidget;
switch (type) {
case ChatAnimationPanelType.none:
// 常规安全区
resultWidget = panelController.buildInPanel(ChatBottomPanelType.none) ??
const SizedBox();
...
break;
case ChatAnimationPanelType.keyboard:
// 键盘占位视图
resultWidget = panelController.buildInPanel(ChatBottomPanelType.keyboard) ??
const SizedBox.shrink();
...
break;
case ChatAnimationPanelType.emoji:
// 表情面板
resultWidget = ChatAnimationEmojiPanel(
...
);
break;
case ChatAnimationPanelType.tool:
// 工具面板
resultWidget = ChatAnimationToolPanel(
...
);
break;
}
...
// 设置 key,这对 AnimatedSwitcher 来说很重要
resultWidget = SizedBox(
key: ValueKey('$type'),
width: double.infinity,
child: resultWidget,
);
return resultWidget;
}
# AnimatedSwitcher
动画
如果你使用 AnimatedSwitcher
来实现面板切换动画,那么建议你与上述代码一样,给每个面板都设置一个 key
(通常使用 ValueKey
),这是为了避免在切换面板时,AnimatedSwitcher
认为前后面板是同一个而导致动画失效。
# 面板视图保持状态
其实是否能保持面板的状态取决于你的布局实现,比如可以使用 IndexedStack
,如果想要在保持状态的同时还有动画,就去找相应的动画库即可,比如示例中使用的 transitioned_indexed_stack
。
# 六、最后
好了,上述便是该库的更新内容,如果你有其它好用的动画库,可以留言跟大家分享 🫶
这里惯例附上 GitHub
地址: https://github.com/LinXunFeng/flutter_chat_packages/tree/main/packages/chat_bottom_container (opens new window) ,如果接入上有什么问题,可以在链接中查看 demo
演示代码。
开源不易,如果你也觉得这个库好用,请不吝给个 Star
👍 ,并多多支持!
本篇到此结束,感谢大家的支持,我们下次再见! 👋

- 01
- Flutter - GetX Helper 如何应用于旧页面06-14
- 02
- Flutter - GetX Helper 助你规范应用 tag06-08
- 03
- Flutter - Xcode16 还原编译速度04-05