Android提供了一个功能强大的基于剪贴板的复制和粘贴框架。 它支持简单和复杂的数据类型,包括文本字符串,复杂数据结构,文本和二进制流数据,甚至应用程序资产。 简单的文本数据直接存储在剪贴板中,而复杂数据则作为粘贴应用程序与内容提供者解析的参考进行存储。 复制和粘贴在应用程序中以及在实现框架的应用程序之间工作。
由于框架的一部分使用内容提供者,因此本主题假定您熟悉Android内容提供程序API,这里是其描述:内容提供者。
一、剪贴板框架
使用剪贴板框架时,将数据放入剪辑对象中,然后将剪辑对象放在系统范围的剪贴板上。 剪辑对象可以采用三种形式之一:
1、Text
一个文本字符串。 您将字符串直接放入剪辑对象中,然后将其放在剪贴板上。 要粘贴字符串,您可以从剪贴板中获取剪辑对象,并将该字符串复制到应用程序的存储空间中。
2、URI
一个Uri对象表示URI的任何形式。这主要用于复制内容提供商的复杂数据。 要复制数据,您将Uri对象放入剪辑对象中,并将剪辑对象放在剪贴板上。 要粘贴数据,您将获取剪辑对象,获取Uri对象,将其解析为数据源(如内容提供者),并将数据从源代码复制到应用程序的存储中。
3、Intent
意图。 这支持复制应用程序快捷方式。 要复制数据,您将创建一个Intent,将其放入剪辑对象中,并将剪辑对象放在剪贴板上。 要粘贴数据,您将获取剪辑对象,然后将Intent对象复制到应用程序的内存区域。
剪贴板一次只保留一个剪辑对象。 当应用程序将剪辑对象放在剪贴板上时,上一个剪辑对象消失。
如果要允许用户将数据粘贴到应用程序中,则不必处理所有类型的数据。 在给用户选择粘贴之前,您可以检查剪贴板上的数据。 除了具有某种数据形式之外,剪辑对象还包含可以告诉您哪些MIME类型或类型可用的元数据。 此元数据可帮助您决定应用程序是否可以对剪贴板数据进行有用的操作。 例如,如果您有一个主要处理文本的应用程序,则可能需要忽略包含URI或Intent的剪辑对象。
您可能还希望允许用户粘贴文本,而不管剪贴板上的数据形式如何。 为此,您可以强制剪贴板数据进入文本表示,然后粘贴此文本。 这将在将剪贴板强制为文本的部分中进行描述。
二、剪贴板类
本节介绍剪贴板框架使用的类。
一)、ClipboardManager
在Android系统中,系统剪贴板由全局ClipboardManager类表示。 你不直接实例化这个类; 而是通过调用getSystemService(CLIPBOARD_SERVICE)来获得对它的引用。
二)、ClipData,ClipData.Item和ClipDescription
要将数据添加到剪贴板,您将创建一个ClipData对象,其中包含数据和数据本身的描述。 剪贴板一次只保留一个ClipData。 ClipData包含一个ClipDescription对象和一个或多个ClipData.Item对象。
ClipDescription对象包含有关剪辑的元数据。 特别地,它包含剪辑数据的可用MIME类型的数组。 当您将剪辑放在剪贴板上时,此阵列可用于粘贴应用程序,可以检查它是否可以处理任何可用的MIME类型。
ClipData.Item对象包含文本,URI或Intent数据:
1、Text
一个CharSequence
.
2、URI
一个Uri。这通常包含内容提供商URI,尽管允许任何URI。 提供数据的应用程序将URI放在剪贴板上。 想要粘贴数据的应用程序从剪贴板获取URI,并使用它访问内容提供程序(或其他数据源)并检索数据。
3、Intent
意图。 此数据类型允许您将应用程序快捷方式复制到剪贴板。 用户可以将快捷方式粘贴到应用程序中以备以后使用。
您可以向剪辑添加多个ClipData.Item对象。 这允许用户将多个选择复制并粘贴为单个剪辑。 例如,如果您有一个列表窗口小部件,允许用户一次选择多个项目,则可以将所有项目一次复制到剪贴板。 为此,您可以为每个列表项创建一个单独的ClipData.Item,然后将ClipData.Item对象添加到ClipData对象。
三)、ClipData便捷方法
...
// if the user selects copy
case R.id.menu_copy:
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
3、将数据复制到新的ClipData对象:
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
2)、对于Uri
// 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);
3)、对于意图
// 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);
4、将新剪辑对象放在剪贴板上:
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData = "";
2、接下来,确定是否应启用或禁用当前活动中的“粘贴”选项。 您应该验证剪贴板是否包含剪辑,并且可以处理由剪辑表示的数据类型:
// Gets the ID of the "paste" menu item
MenuItem mPasteItem = 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())) {
mPasteItem.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
mPasteItem.setEnabled(false);
} else {
// This enables the paste menu item, since the clipboard contains plain text.
mPasteItem.setEnabled(true);
}
}
3、复制剪贴板中的数据。 程序中的这一点只有在“粘贴”菜单项启用时才可以访问,因此您可以假设剪贴板包含纯文本。 您还不知道它是否包含指向纯文本的文本字符串或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;
// 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;
} 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("Clipboard contains an invalid data type");
return;
}
}
// 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";
2、获取全局剪贴板。 还得到一个内容解析器,以便您可以访问内容提供者:
// Gets a handle to the Clipboard Manager
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// Gets a content resolver instance
ContentResolver cr = getContentResolver();
3、从剪贴板获取主剪辑,并将其内容作为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();
4、通过调用getType(Uri)来测试URI是否是内容URI。 如果Uri不指向有效的内容提供者,则此方法返回null:
// If the clipboard contains a URI reference
if (pasteUri != null) {
// Is this a content URI?
String uriMimeType = cr.getType(pasteUri);
// 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();
}
}
}
}
// 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://com.example.contacts"
如果要将名称编码到此URI上,则可以使用以下代码段:
String uriString = "content://com.example.contacts" + "/" + "Smith";
// uriString now contains content://com.example.contacts/Smith.
// Generates a uri object from the string representation
Uri copyUri = Uri.parse(uriString);
如果您已经在使用内容提供商,则可能需要添加一个新的URI路径,以指示URI用于复制。 例如,假设您已经具有以下URI路径:
"content://com.example.contacts"/people
"content://com.example.contacts"/people/detail
"content://com.example.contacts"/people/images
您可以添加另一个特定于复制URI的路径:
"content://com.example.contacts/copying"
然后,您可以通过模式匹配检测“copy”URI,并使用特定于复制和粘贴的代码处理它。
// 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 a MIME type for the copied data
public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
2、在用户复制数据的活动中,设置代码将数据复制到剪贴板。 响应复制请求,将URI放在剪贴板上:
public class MyCopyActivity extends Activity {
...
// The user has selected a name and is requesting a copy.
case R.id.menu_copy:
// Appends the last name to the base URI
// The name is stored in "lastName"
uriString = CONTACTS + COPY_PATH + "/" + lastName;
// Parses the string into a URI
Uri copyUri = Uri.parse(uriString);
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager)
getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
3、在您的内容提供器的全球范围内,创建一个URI匹配器,并添加一个与您放在剪贴板上的URI匹配的URI模式:
public class MyCopyProvider extends ContentProvider {
...
// A Uri Match object that simplifies matching content URIs to patterns.
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// An integer to use in switching based on the incoming URI pattern
private static final int GET_SINGLE_CONTACT = 0;
...
// Adds a matcher for the content URI. It matches
// "content://com.example.contacts/copy/*"
sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
4、设置query()方法。 该方法可以根据您的代码如何处理不同的URI模式,但仅显示剪贴板复制操作的模式:
// Sets up your provider's query() method.
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
...
// Switch based on the incoming content URI
switch (sUriMatcher.match(uri)) {
case GET_SINGLE_CONTACT:
// query and return the contact for the requested name. Here you would decode
// the incoming URI, query the data model based on the last name, and return the result
// as a Cursor.
...
}
5、设置getType()方法为复制的数据返回适当的MIME类型:
// Sets up your provider's getType() method.
public String getType(Uri uri) {
...
switch (sUriMatcher.match(uri)) {
case GET_SINGLE_CONTACT:
return (MIME_TYPE_CONTACT);
...
}
}
从内容URI粘贴数据的部分描述了如何从剪贴板获取内容URI,并使用它来获取和粘贴数据。
三)、复制数据流
您可以将大量文本和二进制数据复制并粘贴为流。 数据可以具有以下形式:
1、存储在实际设备上的文件。
2、来自套接口的流。
3、存储在提供器底层数据库系统中的大量数据。
数据流的内容提供者使用诸如AssetFileDescriptor之类的文件描述符对象来访问其数据,而不是Cursor对象。 粘贴应用程序使用该文件描述符读取数据流。
要设置应用程序以与提供器一起复制数据流,请按照下列步骤操作:
1、为您放置在剪贴板上的数据流设置内容URI。 这样做的选项包括:
1)、将数据流的标识符编码到URI上,如在URI上编码标识符一节所述,然后在提供程序中维护包含标识符和相应流名称的表。
2)、直接在URI上编码流名称。
3)、使用始终从提供程序返回当前流的唯一URI。 如果您使用此选项,则必须记住,通过URI将流复制到剪贴板时,请更新您的提供器以指向不同的流。
2、为您计划提供的每种类型的数据流提供MIME类型。 粘贴应用程序需要此信息来确定是否可以将数据粘贴到剪贴板上。
3、实现一个ContentProvider方法返回流的文件描述符。 如果您对内容URI上的标识符进行编码,请使用此方法确定要打开的流。
4、要将数据流复制到剪贴板,请构建内容URI并将其放在剪贴板上。
要粘贴数据流,应用程序从剪贴板获取剪辑,获取URI,并在调用打开流的ContentResolver文件描述符方法时使用它。 ContentResolver方法调用相应的ContentProvider方法,传递内容URI。 您的提供器将文件描述符返回到ContentResolver方法。 因此,粘贴应用程序有责任从流中读取数据。
以下列表显示了内容提供者最重要的文件描述符方法。 它们中的每一个都具有相应的ContentResolver方法,其中附加了方法名称的字符串“Descriptor”; 例如,OpenAssetFile()的ContentResolver类似程序是openAssetFileDescriptor():
此方法应返回资产文件描述符,但只有提供者支持的MIME类型。 调用者(执行粘贴的应用程序)提供MIME类型模式。 内容提供者(将URI复制到剪贴板的应用程序)返回一个AssetFileDescriptor文件句柄,如果它可以提供该MIME类型,或者如果不能,则会抛出异常。
此方法处理文件的子部分。 您可以使用它来读取内容提供者已经复制到剪贴板的资产。
这个方法是一个更通用的openTypedAssetFile()形式。 它不会过滤允许的MIME类型,但它可以读取文件的小节。
这是一个更通用的openAssetFile()形式。 它无法读取文件的小节。
您可以选择使用openPipeHelper()方法与文件描述符方法。 这允许粘贴应用程序使用管道读取后台线程中的流数据。 要使用此方法,您需要实现ContentProvider.PipeDataWriter接口。 在NotePad示例应用程序中,在NotePadProvider.java的openTypedAssetFile()方法中给出了一个这样做的例子。
六、设计有效的复制/粘贴功能
要为应用程序设计有效的复制和粘贴功能,请记住以下几点:
1、在任何时候,剪贴板上只有一个剪辑。 系统中任何应用程序的新的复制操作将覆盖上一个剪辑。 由于用户可能会离开您的应用程序并在返回之前执行复制,因此您不能假定剪贴板包含用户以前在应用程序中复制的剪辑。
2、每个剪辑的多个ClipData.Item对象的预期目的是支持复制和粘贴多个选择,而不是对单个选择的不同形式的引用。 您通常希望剪辑中的所有ClipData.Item对象具有相同的形式,即它们应该是简单的文本,内容URI或Intent,而不是混合形式。
3、提供数据时,您可以提供不同的MIME表示。 将支持的MIME类型添加到ClipDescription,然后在内容提供器中实现MIME类型。
4、当您从剪贴板获取数据时,您的应用程序负责检查可用的MIME类型,然后确定要使用的MIME类型。 即使剪贴板上有剪辑,用户要求粘贴,则您的应用程序不需要进行粘贴。 如果MIME类型兼容,您应该进行粘贴。 您可以选择使用coerceToText()将剪贴板上的数据强制转换为文本。 如果您的应用程序支持多种可用MIME类型,您可以允许用户选择要使用的MIME类型。