前言
好几个星期没有更文了,最近在IOS的App中找了款FastClip软件挺好用的,于是自己模仿实现了一个简易的剪贴板,并将一些实现的思路记录及踩坑在里面了,体验FastClip时发现了监听粘贴时有以下几个特点:
- 应用只会记录最近一条复制的数据
- 如若复制粘贴板已经存在的数据,该条数据库会提前
- 还有一些自动识别网站,如(知乎等)
FastClip效果图如下(有感兴趣的同学可以自行下载体验——需要1RMB):
根据以上特点,思路清晰了,主要核心思想就是如何获取粘贴板的数据(调用时机是由后台进入应用)
接下来本文将介绍以下内容:
- 如何在后台进入应用时调用方法
- ClipBoard 如何实现复制粘贴的功能
- 在由后台进入应用时获取ClipBoard 的数据
- 将列表中的数据渲染到页面,并实现复制、移除操作
前后台切换调用方法(didChangeAppLifecycleState)
这里涉及到了生命周期回调didChangeAppLifecycleState 方法
在didChangeAppLifecycleState 回调方法中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对App 生命周期状态的封装,常用的状态包括 inactive、paused、resumed
inactive:处在不活动状态,无法处理用户响应paused:不可见且不能响应用户的输入,但在后台继续活动中resumed:可见的,且能响应用户的输入
具体的调用过程如下:
使用该回调方法时我们需要在生命周期initState中注册监听器,在dispose中移除监听器,具体代码如下
@override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance!.addObserver(this); // 注册监听器
}
@override
void dispose() {
// TODO: implement dispose
WidgetsBinding.instance!.removeObserver(this); // 移除监听器
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// TODO: implement didChangeDependencies
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// 获取粘贴板数据
}
}
ClipBoard 如何实现复制粘贴
ClipBoard中实现复制粘贴很简单,调用setData方法实现复制,调用getData方法实现粘贴,具体代码如下
// 获取粘贴的数据
void getPasteData() async {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
print('data content ${data?.text}');
}
// 从粘贴板中copy数据
void setPasteData(text) {
ClipboardData clipboardData = ClipboardData(text: text);
Clipboard.setData(clipboardData);
}
由后台进入应用时获取ClipBoard 的数据
此时我以为很简单,只需调用写好的获取粘贴板数据的方法即可,但是在我运行时发现我获取到的data为null,这一度让我陷入了迷茫,我尝试着打断点去查看数据,却发现了以下代码执行了两次
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
这时数据却是获取到了,我那时猜测加个延时应该可以处理,但还是进行谷歌了(百度没找到,吐槽:github里的东西,百度搜索没搜到,谷歌一下就出来了, 参考文章),和我猜测的没错,有大佬利用延时解决了该方法(当然文章中还有很多其它的解决办法,因为我一开始想法也是靠延时去处理,所以其它方法我没试),以下是改造之后的代码:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// TODO: implement didChangeDependencies
super.didChangeAppLifecycleState(state);
// 由后台进入应用
if (state == AppLifecycleState.resumed) {
// 此时获取粘贴板数据
Future.delayed(Duration(milliseconds: 500)).then((_) =>getPasteData());
}
}
将列表中的数据渲染到页面,并实现复制、移除操作
封装数据
获取ClipBoard 的数据,并封装成一个List集合
代码如下
final List<PasteModel> pasteList = [];
// 获取粘贴的数据
void getPasteData() async {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
print('data content ${data?.text}'); // data content 复制的内容
// 判断数据是否为空
if (data != null && data.text!.isNotEmpty) {
int index = -1;
PasteModel? tempPaste = null;
for (int i = 0; i < pasteList.length; i++) {
if (pasteList[i].content == data.text) {
tempPaste = pasteList[i];
index = i;
break;
}
}
if (index > 0) {
pasteList.remove(index);
} else if (index == -1) {
tempPaste = PasteModel(content: data.text!, show: false);
}
if (index != 0) {
pasteList.insert(0, tempPaste!);
setState(() {
pasteList;
});
}
}
}
相关实体类
class PasteModel {
//复制的内容
final String content;
// 是否显示相关操作按钮
bool show;
PasteModel({required this.content, this.show = false});
}
列表渲染+复制粘贴等功能
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonBar(
title: 'Fast Clip',
),
body: Center(
child: Column(
children: [
Container(
color: secondBgColor,
padding: EdgeInsets.fromLTRB(12, 6, 12, 12),
child: CommonSearch(
hint: '搜索记录',
icon: Icon(CupertinoIcons.search),
onTap: () => {
// TODO 接入搜索
},
),
),
Expanded(
// color: secondBgColor,
child: pasteList.length > 0
? ListView(
children: buildPasteList(),
)
: Text('No Data'),
)
],
),
));
}
List<Widget> buildPasteList() {
List<Widget> pasteWidget = [];
pasteList.forEach((element) {
pasteWidget.add(new Container(
padding: EdgeInsets.fromLTRB(0, 12, 0, 12),
child: new GestureDetector(
onHorizontalDragEnd: (endDetails) {
print(element.content.toString());
setState(() {
element.show = !element.show;
});
},
child: Container(
// height: 40.0,
decoration: new BoxDecoration(
border: new Border(
bottom: BorderSide(color: Colors.amber, width: 0.5),
)),
child: new Row(
children: element.show
? [
Container(
width: 250,
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Text(
element.content,
style: TextStyle(color: primaryColor),
)),
Spacer(),
new CommonButton(
text: 'copy',
radius: 0,
onTap: () {
setPasteData(element.content);
},
),
new CommonButton(
text: 'remove',
gradient: LinearGradient(
colors: [Color(0xffed2856), Colors.red]),
radius: 0,
onTap: () {
pasteList.remove(element);
setState(() {
pasteList;
});
},
)
]
: [
Container(
width: 250,
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Text(
element.content,
style: TextStyle(color: primaryColor),
))
]),
),
),
));
});
return pasteWidget;
}
}
效果图如下:
总结
其实实现剪贴板的原理很简单,即在前后台切换时获取ClipBoard中的数据,并处理相关数据即可