【Android 界面】剪切板的基本使用

Android 提供了一个强大的基于剪贴板的框架,用于复制和粘贴。它支持简单和复杂的数据类型,包括文本字符串、复杂数据结构、文本、二进制流数据,甚至应用资源。简单的文本数据直接存储在剪贴板中,而复杂的数据则存储为引用,执行粘贴操作的应用使用 Content Provider 对其进行解析。复制和粘贴既可以在应用内进行,也可以在实现了该框架的应用之间进行。

剪切板类

ClipboardManager

在 Android 系统中,系统剪贴板由全局 ClipboardManager 类表示。您不能直接实例化此类;而是应通过调用 getSystemService(CLIPBOARD_SERVICE)获取对它的引用

ClipData、ClipData.Item 和 ClipDescription

如需将数据添加到剪贴板,您需要创建一个 ClipData 对象,其中包含数据的说明ClipDescription和数据本身ClipData.Item剪贴板一次只保留一个 ClipData。ClipData 包含一个 ClipDescription 对象以及一个或多个 ClipData.Item 对象。

ClipDescription对象包含关于剪切的元数据。具体来说,它包含剪切的数据的可用 MIME 类型数组。当您将一个剪切放到剪贴板中时,系统会向粘贴应用提供此信息。粘贴应用可以检查该信息,以了解自己能否处理该剪贴数据。

ClipData.Item对象包含 text、URI 或 intent 数据:

Text

一个文本字符串。您可以直接将字符串放入剪贴对象中,然后将剪贴对象放到剪贴板中。如需粘贴字符串,您需要从剪贴板获取剪贴对象,然后将字符串复制到应用的存储空间。

URI

一个 Uri 对象,表示任何形式的 URI。它主要适用于从 Content Provider 复制复杂的数据。如需复制数据,您需要将一个 Uri 对象放入一个剪贴对象中,并将该剪贴对象放到剪贴板中。如需粘贴数据,您需要获取该剪贴对象,获取 Uri 对象,将其解析为数据源(例如某个 Content Provider),然后将数据从源中复制到应用的存储空间。

Intent

一个 Intent。它支持复制应用快捷方式。如需复制数据,您需要创建一个 Intent,将其放入一个剪贴对象中,然后将该剪贴对象放到剪贴板中。如需粘贴数据,您需要获取该剪贴对象,然后将 Intent 对象复制到您应用的内存区域。

注意:剪贴板一次只保留一个剪贴对象。当应用将一个剪贴对象放到剪贴板时,上一个剪贴对象会消失。不过,您可以向一个剪切添加多个 ClipData.Item 对象。这让用户可以将多个选择复制和粘贴为一个剪切。例如,如果您有一个列表微件,可让用户一次选择多个项,则您可以同时将所有这些项复制到剪贴板。为此,您需要分别为每个列表项创建一个 ClipData.Item,然后将 ClipData.Item 对象添加到 ClipData 对象。
此外,您可能还希望无论剪贴板中的数据采用何种形式,用户都可以粘贴文本。为此,您可以将剪贴板数据强制转换为文本表示形式,然后粘贴相应文本。

ClipData 便捷方法

ClipData 类提供静态便捷方法,用于创建具有单个 ClipData.Item 对象和一个简单 ClipDescription 对象的 ClipData 对象:

newPlainText(label, text)

返回一个ClipData 对象,该对象的单个 ClipData.Item对象包含一个文本字符串。ClipDescription 对象的标签设置为 label。ClipDescription 中的单一 MIME 类型为 MIMETYPE_TEXT_PLAIN
使用newPlainText()可从文本字符串创建剪切。

newUri(resolver, label, URI)

返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象包含一个 URIClipDescription对象的标签设置为label。如果该 URI 是内容 URI(Uri.getScheme() 返回 content:),则此方法会使用 resolver 中提供的 ContentResolver对象从 Content Provider 检索可用的 MIME 类型,并将其存储在 ClipDescription 中。对于不是 content: URIURI,此方法会将 MIME 类型设置为 MIMETYPE_TEXT_URILIST。
使用newUri()可从 URI(尤其是 content: URI)创建剪切。

newIntent(label, intent)

返回一个 ClipData对象,该对象的单个ClipData.Item对象包含一个 IntentClipDescription对象的标签设置为 labelMIME类型设置为 MIMETYPE_TEXT_INTENT
使用 newIntent() 可从 Intent 对象创建剪切。

将剪贴板数据强制转换为文本

即使您的应用仅处理文本,您也可以从剪贴板复制非文本数据,只需使用 ClipData.Item.coerceToText()方法对其进行转换即可。

此方法可将ClipData.Item中的数据转换为文本并返回一个 CharSequenceClipData.Item.coerceToText() 返回的值基于 ClipData.Item 中的数据的形式

Text

如果 ClipData.Item是一个文本(getText() 不为 null),则 coerceToText() 返回该文本

URI

如果ClipData.Item 是一个 URI(getUri() 不为 null),则 coerceToText() 会尝试将其作为内容 URI 使用:

  • 如果此 URI 是内容 URI,并且提供程序可以返回文本流,则 coerceToText() 返回文本流
  • 如果此 URI 不是内容 URI,或者是内容 URI 但提供程序不提供文本流,则 coerceToText() 返回此 URI 的 Uri.toString() 返回的表示形式
Intent

如果ClipData.Item 是一个 Intent(getIntent() 不为 null),则 coerceToText() 会将其转换为 Intent URI 并返回。该表示形式与 Intent.toUri(URI_INTENT_SCHEME) 返回的表示形式相同。

剪切板框架

在这里插入图片描述
图 1 汇总了剪贴板框架。如需复制数据,应用需要将一个 ClipData 对象放到 ClipboardManager 全局剪贴板中。ClipData 包含一个或多个 ClipData.Item 对象和一个 ClipDescription 对象。如需粘贴数据,应用需要获取 ClipData,从 ClipDescription 获取其 MIME 类型,然后从 ClipData.Item 或 ClipData.Item 引用的 Content Provider 获取数据。

复制到剪贴板

如前所述,如需将数据复制到剪贴板,您需要获取全局 ClipboardManager 对象的句柄,创建一个 ClipData 对象,向其中添加一个 ClipDescription 和一个或多个 ClipData.Item 对象,然后将已完成的 ClipData 对象添加到 ClipboardManager 对象。以下过程对此进行了详细介绍:

  1. 如果您要使用内容 URI 复制数据,请设置一个 Content Provider。

  2. 获取系统剪贴板:

// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
        getSystemService(Context.CLIPBOARD_SERVICE);
  1. 将数据复制到新的 ClipData 对象:
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");

——————————————————————————————————————————————————————

// Creates a Uri based on a base Uri and a record ID based on the contact's last name
// Declares the base URI string
private static final String CONTACTS = "content://com.example.contacts";

// Declares a path string for URIs that you use to copy data
private static final String COPY_PATH = "/copy";

// Declares the Uri to paste to the clipboard
Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);

...

// Creates a new URI clip object. The system uses the anonymous getContentResolver() object to
// get MIME types from provider. The clip object's label is "URI", and its data is
// the Uri previously created.
ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
——————————————————————————————————————————————————————
// Creates the Intent
Intent appIntent = new Intent(this, com.example.demo.myapplication.class);

...

// Creates a clip object with the Intent in it. Its label is "Intent" and its data is
// the Intent object created previously
ClipData clip = ClipData.newIntent("Intent", appIntent);

  1. 将新的剪贴对象放到剪贴板中
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);

从剪贴板粘贴

如前所述,您可以通过以下方法从剪贴板中粘贴数据:获取全局剪贴板对象,获取剪贴对象,查看其数据,然后将数据从剪贴对象复制到您自己的存储空间(如果可以)。本部分将介绍在应用粘贴数据时可能会出现的通知,并且还会详细说明了如何粘贴三种形式的剪贴板数据。

在应用访问剪贴板数据时显示系统通知

在 Android 12(API 级别 31)及更高版本中,系统通常会在应用调用 getPrimaryClip() 时显示消息框消息。该消息包含以下格式的文本:

	APP pasted from your clipboard

在应用执行以下某一项操作时,系统不会显示消息框消息:

  • 通过您自己的应用访问 ClipData。
  • 通过特定应用反复访问ClipData。只有在您的应用首次访问该应用的数据时,系统才会显示消息框。
  • 检索剪贴对象的元数据,例如,通过调用getPrimaryClipDescription()(而非 getPrimaryClip())进行检索。
粘贴纯文本

如需粘贴纯文本,首先请获取全局剪贴板并验证它能否返回纯文本。然后获取剪贴对象,并使用 getText() 将其文本复制到您自己的存储空间,如以下过程所述:

  1. 使用 getSystemService(CLIPBOARD_SERVICE)获取全局 ClipboardManager 对象。另外,声明一个全局变量以包含粘贴的文本:
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData = "";
  1. 接下来,确定您是否应在当前 Activity 中启用或停用“粘贴”选项。您应验证剪贴板是否包含剪切,以及您是否可以处理剪切所代表的数据类型
// Gets the ID of the "paste" menu item
MenuItem pasteItem = menu.findItem(R.id.menu_paste);

// If the clipboard doesn't contain data, disable the paste menu item.
// If it does contain data, decide if you can handle the data.
if (!(clipboard.hasPrimaryClip())) {

    pasteItem.setEnabled(false);

} else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {

    // This disables the paste menu item, since the clipboard has data but it is not plain text
    pasteItem.setEnabled(false);
} else {

    // This enables the paste menu item, since the clipboard contains plain text.
    pasteItem.setEnabled(true);
}
  1. 从剪贴板复制数据。只有在“粘贴”菜单项处于启用状态时,才能在项目中执行到这一步,因此您可以假设剪贴板包含纯文本。您目前还不知道它是否包含文本字符串或指向纯文本的 URI。以下代码段对此进行了测试,但它仅显示用于处理纯文本的代码:
// Responds to the user selecting "paste"
case R.id.menu_paste:

// Examines the item on the clipboard. If getText() does not return null, the clip item contains the
// text. Assumes that this application can only handle one item at a time.
 ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);

// Gets the clipboard as text.
pasteData = item.getText();

// If the string contains data, then the paste operation is done
if (pasteData != null) {
    return true;

// The clipboard does not contain text. If it contains a URI, attempts to get data from it
} else {
    Uri pasteUri = item.getUri();

    // If the URI contains something, try to get text from it
    if (pasteUri != null) {

        // calls a routine to resolve the URI and get data from it. This routine is not
        // presented here.
        pasteData = resolveUri(Uri);
        return true;
    } else {

        // Something is wrong. The MIME type was plain text, but the clipboard does not contain either
        // text or a Uri. Report an error.
        Log.e(TAG, "Clipboard contains an invalid data type");
        return false;
    }
}
从内容 URI 中粘贴数据

如果 ClipData.Item 对象包含内容 URI,并且您已确定自己可以处理它的某种 MIME 类型,请创建一个 ContentResolver,然后调用相应 Content Provider 方法以检索数据。

以下过程说明了如何基于剪贴板中的内容 URI 从 Content Provider 获取数据。它会检查提供程序中是否有应用可以使用的 MIME 类型:

  1. 声明一个全局变量以包含 MIME 类型:
// Declares a MIME type constant to match against the MIME types offered by the provider
public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
  1. 获取全局剪贴板。另外,获取一个内容解析器,以便您可以访问 Content Provider:
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Gets a content resolver instance
ContentResolver cr = getContentResolver();
  1. 从剪贴板中获取主要剪切,并获取其内容作为 URI:
// Gets the clipboard data from the clipboard
ClipData clip = clipboard.getPrimaryClip();

if (clip != null) {

    // Gets the first item from the clipboard data
    ClipData.Item item = clip.getItemAt(0);

    // Tries to get the item's contents as a URI
    Uri pasteUri = item.getUri();
  1. 通过调用 getType(Uri) 测试该 URI 是否为内容 URI。如果 Uri 不指向有效的 Content Provider,则此方法会返回 null:
// If the clipboard contains a URI reference
    if (pasteUri != null) {

        // Is this a content URI?
        String uriMimeType = cr.getType(pasteUri);
  1. 测试 Content Provider 是否支持当前应用可以理解的 MIME 类型。如果支持,请调用 ContentResolver.query() 以获取数据。返回值是 Cursor:
// If the return value is not null, the Uri is a content Uri
        if (uriMimeType != null) {

            // Does the content provider offer a MIME type that the current application can use?
            if (uriMimeType.equals(MIME_TYPE_CONTACT)) {

                // Get the data from the content provider.
                Cursor pasteCursor = cr.query(uri, null, null, null, null);

                // If the Cursor contains data, move to the first record
                if (pasteCursor != null) {
                    if (pasteCursor.moveToFirst()) {

                    // get the data from the Cursor here. The code will vary according to the
                    // format of the data model.
                    }
                }

                // close the Cursor
                pasteCursor.close();
             }
         }
     }
}
粘贴 Intent

如需粘贴 Intent,首先请获取全局剪贴板。检查 ClipData.Item 对象以了解它是否包含 Intent。然后调用 getIntent(),以将相应 Intent 复制到您自己的存储空间。以下代码段演示了此过程:

// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();

if (pasteIntent != null) {

    // handle the Intent

} else {

    // ignore the clipboard, or issue an error if your application was expecting an Intent to be
    // on the clipboard
}

使用Content Provider复制复杂的数据

https://developer.android.com/guide/topics/text/copy-paste.html#Provider

注意

如需为您的应用设计有效的复制和粘贴功能,请谨记以下几点:

  • 在任何时候,剪贴板中都只能有一个剪切。任何应用在系统中执行的新的复制操作都会覆盖上一个剪切。由于用户可能会离开您的应用并在执行复制操作后返回,因此您不能假设剪贴板中包含用户之前在您的应用中复制的剪切。
  • 每个剪切有多个 ClipData.Item 对象的预期目的是,支持复制和粘贴多个选择,而不是一个选择的不同引用形式。您通常希望一个剪切中的所有 ClipData.Item 对象都采用相同的形式,也就是说,它们应该都是简单文本、内容 URI 或 Intent,而不是这些形式的混合。
  • 提供数据时,您可以提供不同的 MIME 表示形式。将您支持的 MIME 类型添加到 ClipDescription,然后在Content Provider中实现这些 MIME 类型。
  • 当您从剪贴板获取数据时,您的应用负责检查可用的 MIME 类型,并决定使用哪种类型(如果有)。即使剪贴板中有剪切且用户请求粘贴,您的应用也无需执行粘贴操作。您应该在 MIME 类型兼容时执行粘贴操作。您可以选择使用 coerceToText()(如果已选择)将剪贴板中的数据强制转换成文本。如果您的应用支持多种可用的 MIME 类型,您可以允许用户选择要使用哪一种。
  • 在android Q以上的手机上当 activity 页面失去焦点的时候是获取不到 剪贴板的数据的,直接返回null。Android Q中只有当应用处于可交互情况(默认输入法本身就可交互)才能访问剪切板和监听剪切板变化,在onResume回调也无法直接访问剪切板,这么做的好处是避免了一些应用后台疯狂监听响应剪切板的内容,疯狂弹窗。因此如果还需要监听剪切板,可以使用应用生命周期回调,监听APP后台返回,延迟几毫秒访问剪切板,再保存最后一次访问得到的剪切板内容,每次都比较一下是否有变化,再进行下一步操作。
new Handler().postDelayed(new Runnable() {
    
    public void run() {
    
        cm = (ClipboardManager) MainActivity.this.getSystemService(Context.CLIPBOARD_SERVICE);
        if (!cm.hasPrimaryClip()) {
    
            return;
        }
        //剪切板操作
        ...
}, 1000);

或者:

public class StatisticActivityLifecycleCallback implements Application.ActivityLifecycleCallbacks {
    private boolean isNullData = false; // 是否拿到剪贴板数据

  @Override
  public void onActivityStarted(final Activity activity){
    ....
  
    ClipboardManager clipboardManager = (ClipboardManager) MSApplication.mContext.getSystemService(Context.CLIPBOARD_SERVICE);
    ClipData clipData = clipboardManager.getPrimaryClip();
    
    if(clipData == null) {
    	isNullData = true;
        // 这里获取剪贴板数据
    } else {
    	isNullData = false;
    }
    ....
  }
  
  @Override
  public void onActivityResumed(Activity activity) {
  	// 这里重新获取一次剪贴板数据
  }
}

参考文章:
《Android 官方文档》
android 剪切板遇到的坑
android Q以上剪贴板的坑
Android10填坑适配指南,实际经验代码,拒绝翻译

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值