Flutter 练习(八)—— 一个简易剪贴板的实现

2,717 阅读3分钟

前言

好几个星期没有更文了,最近在IOS的App中找了款FastClip软件挺好用的,于是自己模仿实现了一个简易的剪贴板,并将一些实现的思路记录及踩坑在里面了,体验FastClip时发现了监听粘贴时有以下几个特点:

  • 应用只会记录最近一条复制的数据
  • 如若复制粘贴板已经存在的数据,该条数据库会提前
  • 还有一些自动识别网站,如(知乎等)

FastClip效果图如下(有感兴趣的同学可以自行下载体验——需要1RMB):

根据以上特点,思路清晰了,主要核心思想就是如何获取粘贴板的数据(调用时机是由后台进入应用)

接下来本文将介绍以下内容:

  • 如何在后台进入应用时调用方法
  • ClipBoard 如何实现复制粘贴的功能
  • 在由后台进入应用时获取ClipBoard 的数据
  • 将列表中的数据渲染到页面,并实现复制、移除操作

前后台切换调用方法(didChangeAppLifecycleState

这里涉及到了生命周期回调didChangeAppLifecycleState 方法

didChangeAppLifecycleState 回调方法中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对App 生命周期状态的封装,常用的状态包括 inactivepausedresumed

  • 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 的数据

此时我以为很简单,只需调用写好的获取粘贴板数据的方法即可,但是在我运行时发现我获取到的datanull,这一度让我陷入了迷茫,我尝试着打断点去查看数据,却发现了以下代码执行了两次

 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;
  }
}

效果图如下:

参考文档:github.com/flutter/flu…

总结

其实实现剪贴板的原理很简单,即在前后台切换时获取ClipBoard中的数据,并处理相关数据即可