Flutter Chat App: 添加消息删除功能
Flutter Chat App: 添加消息删除功能
本篇博客将讲解如何在 Flutter Chat App 中为每条消息添加一个删除按钮,实现滑动删除功能。
实现思路
可以在每条消息的外层包裹一个 Dismissible 组件,然后设置 onDismissed 属性为删除该条消息的方法。
代码实现
import 'dart:async';
import 'dart:convert';
import 'package:animate_do/animate_do.dart';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:muse_nepu_course/global.dart';
import 'package:muse_nepu_course/home.dart';
import 'package:muse_nepu_course/service/api_service.dart';
import 'package:muse_nepu_course/theme/color_schemes.g.dart';
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
var _filteredStringList = [];
var _stringList = [];
int _selectedIndex = -1;
String? _selectedValue;
//从assets/prompts.json中读取数据,并转换为List<Map<String, String>>
void loadprompts() async {
String prompt = await rootBundle.loadString('assets/prompts.json');
_stringList = json.decode(prompt);
_filteredStringList = List.from(_stringList);
}
class _ChatPageState extends State<ChatPage>
with TickerProviderStateMixin, WidgetsBindingObserver {
final List<Map<String, String>> _messages = [];
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
final FocusNode _focusNode = FocusNode();
String xdata = '';
late AnimationController controller;
bool _sending = false;
bool _showAnimation = true;
bool _isAtBottom = true;
int _maxLines = 1; // 最多显示的行数
@override
void initState() {
super.initState();
Global.bottombarheight = 60;
_messages.clear();
Global.messages_pure = '';
ApiService().active_chatgpt();
controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_focusNode.requestFocus();
loadprompts(); // 加载提示语句
WidgetsBinding.instance!.addObserver(this);
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
if (_isAtBottom) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme),
darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme),
home: Scaffold(
appBar: AppBar(
backgroundColor: Global.home_currentcolor,
title: Text(
'双击消息复制',
style: TextStyle(color: Colors.white),
),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => HomePage()));
//回到上一级
Navigator.pop(context);
},
),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Global.home_currentcolor,
title: Text('自动注入消息'),
),
body: ListView.builder(
itemCount: _filteredStringList.length,
itemBuilder: (BuildContext context, int index) {
String act = _filteredStringList[index]['act']!;
String prompt =
_filteredStringList[index]['prompt']!;
return RadioListTile(
title: Text('行为: $act'),
subtitle: Text('语句: $prompt'),
value: index,
groupValue: _selectedIndex,
onChanged: (int? value) {
setState(() {
_selectedIndex = value!;
_selectedValue =
_filteredStringList[_selectedIndex]['act'];
//发送消息
_controller.text =
_filteredStringList[_selectedIndex]['prompt'];
//返回主页面
Navigator.pop(context);
});
},
);
},
));
});
},
),
],
),
body: Container(
child: Column(
children: [
Expanded(
child: Container(
padding: EdgeInsets.all(12.0),
child: _messages.isEmpty
? FadeInUp(
child: AnimatedOpacity(
opacity: _showAnimation ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Center(
child: DefaultTextStyle(
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
child: AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
'chatgpt',
colors: [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
],
textStyle: TextStyle(fontSize: 50.0),
),
],
isRepeatingAnimation: true,
),
),
),
)) : ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
itemBuilder: (BuildContext context, int index) {
String? sender = _messages[index]['sender'];
String? message = _messages[index]['message'];
return FadeInUp(
child: Dismissible(
key: Key(index.toString()), // 唯一标识符
direction: DismissDirection.endToStart, // 只允许从右向左滑动删除
onDismissed: (direction) {
setState(() {
_messages.removeAt(index);
Global.messages_pure = _messages.map((e) => e['message']).join('
');
});
},
background: Container(
color: Colors.red,
child: Icon(Icons.delete, color: Colors.white),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 16.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: GestureDetector(
onDoubleTap: () {
Clipboard.setData(ClipboardData(text: message));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已复制到剪贴板'),
duration: Duration(seconds: 1),
),
);
},
child: Row(
mainAxisAlignment: sender == '我' ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (sender != '我') SizedBox(width: 8.0),
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
decoration: BoxDecoration(
color: sender == '我' ? Global.home_currentcolor : Theme.of(context).brightness == Brightness.light ? Colors.grey[200] : Colors.grey[800],
borderRadius: BorderRadius.circular(20.0),
),
child: MarkdownBody(
data: message!,
),
),
),
if (sender == '我') SizedBox(width: 8.0),
],
),
),
),
));
},
),
),
),
Container(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
IconButton(
icon: Icon(Icons.delete_sweep_outlined),
color: Global.home_currentcolor,
onPressed: () {
setState(() {
_showAnimation = true;
_messages.clear();
Global.messages_pure = '';
});
},
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
),
child: TextField(
focusNode: _focusNode,
controller: _controller,
onChanged: (value) {
final lines = value.split('
').length;
setState(() {
_maxLines = lines + 1;
});
},
maxLines: _maxLines,
// decoration: InputDecoration.collapsed(
// hintText: '输入消息...',
// ),
),
),
),
SizedBox(width: 8.0),
AnimatedBuilder(
// 包裹IconButton的AnimatedBuilder,用于构建进度指示器
animation: controller,
builder: (BuildContext context, Widget? child) {
return IconButton(
icon: _sending ? SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>(Global.home_currentcolor),
),
) : Icon(Icons.send),
color: Global.home_currentcolor,
onPressed: () async {
sendmessage();
},
);
},
),
],
),
),
],
),
),
));
}
代码说明
- 在
ListView.builder中的itemBuilder中,用Dismissible组件包裹每个消息的Padding组件。 - 设置
Dismissible的key属性为Key(index.toString()),确保每个Dismissible组件都有唯一的标识符。 - 设置
Dismissible的direction属性为DismissDirection.endToStart,只允许从右向左滑动删除。 - 设置
Dismissible的onDismissed属性为一个回调函数,该函数在消息被删除时执行。在回调函数中,使用setState更新消息列表_messages,并更新全局变量Global.messages_pure,以便其他页面可以获取最新的消息列表。 - 设置
Dismissible的background属性为一个Container组件,用来显示删除图标。
完整代码
import 'dart:async';
import 'dart:convert';
import 'package:animate_do/animate_do.dart';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:muse_nepu_course/global.dart';
import 'package:muse_nepu_course/home.dart';
import 'package:muse_nepu_course/service/api_service.dart';
import 'package:muse_nepu_course/theme/color_schemes.g.dart';
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
var _filteredStringList = [];
var _stringList = [];
int _selectedIndex = -1;
String? _selectedValue;
//从assets/prompts.json中读取数据,并转换为List<Map<String, String>>
void loadprompts() async {
String prompt = await rootBundle.loadString('assets/prompts.json');
_stringList = json.decode(prompt);
_filteredStringList = List.from(_stringList);
}
class _ChatPageState extends State<ChatPage>
with TickerProviderStateMixin, WidgetsBindingObserver {
final List<Map<String, String>> _messages = [];
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
final FocusNode _focusNode = FocusNode();
String xdata = '';
late AnimationController controller;
bool _sending = false;
bool _showAnimation = true;
bool _isAtBottom = true;
int _maxLines = 1; // 最多显示的行数
@override
void initState() {
super.initState();
Global.bottombarheight = 60;
_messages.clear();
Global.messages_pure = '';
ApiService().active_chatgpt();
controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_focusNode.requestFocus();
loadprompts(); // 加载提示语句
WidgetsBinding.instance!.addObserver(this);
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
if (_isAtBottom) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme),
darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme),
home: Scaffold(
appBar: AppBar(
backgroundColor: Global.home_currentcolor,
title: Text(
'双击消息复制',
style: TextStyle(color: Colors.white),
),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => HomePage()));
//回到上一级
Navigator.pop(context);
},
),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Global.home_currentcolor,
title: Text('自动注入消息'),
),
body: ListView.builder(
itemCount: _filteredStringList.length,
itemBuilder: (BuildContext context, int index) {
String act = _filteredStringList[index]['act']!;
String prompt =
_filteredStringList[index]['prompt']!;
return RadioListTile(
title: Text('行为: $act'),
subtitle: Text('语句: $prompt'),
value: index,
groupValue: _selectedIndex,
onChanged: (int? value) {
setState(() {
_selectedIndex = value!;
_selectedValue =
_filteredStringList[_selectedIndex]['act'];
//发送消息
_controller.text =
_filteredStringList[_selectedIndex]['prompt'];
//返回主页面
Navigator.pop(context);
});
},
);
},
));
});
},
),
],
),
body: Container(
child: Column(
children: [
Expanded(
child: Container(
padding: EdgeInsets.all(12.0),
child: _messages.isEmpty
? FadeInUp(
child: AnimatedOpacity(
opacity: _showAnimation ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Center(
child: DefaultTextStyle(
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
child: AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
'chatgpt',
colors: [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
],
textStyle: TextStyle(fontSize: 50.0),
),
],
isRepeatingAnimation: true,
),
),
),
)) : ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
itemBuilder: (BuildContext context, int index) {
String? sender = _messages[index]['sender'];
String? message = _messages[index]['message'];
return FadeInUp(
child: Dismissible(
key: Key(index.toString()), // 唯一标识符
direction: DismissDirection.endToStart, // 只允许从右向左滑动删除
onDismissed: (direction) {
setState(() {
_messages.removeAt(index);
Global.messages_pure = _messages.map((e) => e['message']).join('
');
});
},
background: Container(
color: Colors.red,
child: Icon(Icons.delete, color: Colors.white),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 16.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: GestureDetector(
onDoubleTap: () {
Clipboard.setData(ClipboardData(text: message));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已复制到剪贴板'),
duration: Duration(seconds: 1),
),
);
},
child: Row(
mainAxisAlignment: sender == '我' ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (sender != '我') SizedBox(width: 8.0),
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
decoration: BoxDecoration(
color: sender == '我' ? Global.home_currentcolor : Theme.of(context).brightness == Brightness.light ? Colors.grey[200] : Colors.grey[800],
borderRadius: BorderRadius.circular(20.0),
),
child: MarkdownBody(
data: message!,
),
),
),
if (sender == '我') SizedBox(width: 8.0),
],
),
),
),
));
},
),
),
),
Container(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
IconButton(
icon: Icon(Icons.delete_sweep_outlined),
color: Global.home_currentcolor,
onPressed: () {
setState(() {
_showAnimation = true;
_messages.clear();
Global.messages_pure = '';
});
},
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
),
child: TextField(
focusNode: _focusNode,
controller: _controller,
onChanged: (value) {
final lines = value.split('
').length;
setState(() {
_maxLines = lines + 1;
});
},
maxLines: _maxLines,
// decoration: InputDecoration.collapsed(
// hintText: '输入消息...',
// ),
),
),
),
SizedBox(width: 8.0),
AnimatedBuilder(
// 包裹IconButton的AnimatedBuilder,用于构建进度指示器
animation: controller,
builder: (BuildContext context, Widget? child) {
return IconButton(
icon: _sending ? SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>(Global.home_currentcolor),
),
) : Icon(Icons.send),
color: Global.home_currentcolor,
onPressed: () async {
sendmessage();
},
);
},
),
],
),
),
],
),
),
));
}
效果展示
现在,每条消息都可以通过向左滑动来删除了。
[图片展示]
总结
本文介绍了如何在 Flutter Chat App 中为每条消息添加一个删除按钮,实现滑动删除功能。使用 Dismissible 组件可以方便地实现这个功能,代码简洁易懂。
希望这篇文章对您有所帮助!
原文地址: https://www.cveoy.top/t/topic/ovqM 著作权归作者所有。请勿转载和采集!