橘子味的心
标题:12.5 Android复制粘贴实例

在看开发实例之前,我们先来了解一下如何设计有效的复制粘贴功能。

要为应用程序设计高效的复制与粘贴功能,需要注意以下几点:
  1. 任何时候剪贴板中都只有一个 clip。系统中任何应用程序执行了新的复制操作,都会覆盖之前的 clip 。由于用户可能会跳离应用程序,并在返回前执行复制,因此不能确定剪贴板中包含前一次在该程序中复制的内容。
  2. 设计 clip<span="">ClipData.Item 对象的初衷是为了支持一次复制/粘贴多个选项,而不是为了单个选项能包含多种不同的格式。通常一个 clip 中的所有 ClipData.Item 对象都应该具有相同的格式,也就是说,所有对象要么是简单文本,要么是 Content URI 或者 Intent,而不能混在一起使用。
  3. 提供数据时,可以提交各种不同的 MIME 描述。把所支持的 MIME< span=""> 给 ClipDescription 对象,然后在 Content Provider 中实现这些 MIME 类型。
  4. 从剪贴板读取数据时,应用程序对可用的MIME类型进行检查,然后决定要使用哪些类型。即使剪贴板中存在 clip,用户也请求了粘贴,应用程序不一定要执行粘贴,而是在 MIME 类型能够兼容时才执行粘贴,可以选用 coerceToText() 把剪贴板数据强制转换成文本。如果应用程序能支持多种 MIME 类型,用户先选择其中一种使用。

下面实例 ClipBoardDemo 修改自 Android SDK 中的 Demo,演示了 Android 的剪贴板对于带格式文本、无格式文本、HTML文本、Intent 和 URI 的粘贴效果,并可以将这几种数据格式进行相互转化。该实例的运行效果如图 1 所示。

ClipBoardDemo 的运行效果
图 1  ClipBoardDemo 的运行效果

该实例的布局文件 clipboard.xml 的内容如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2.  
  3. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical">
  7.  
  8. <LinearLayout
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:orientation="vertical">
  12.  
  13. <LinearLayout
  14. android:layout_width="match_parent"
  15. android:layout_height="wrap_content"
  16. android:orientation="horizontal">
  17.  
  18. <Button
  19. android:id="@+id/copy_styled_text"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:onClick="pasteStyleText"
  23. android:text="复制" />
  24.  
  25. <TextView
  26. android:id="@+id/styled_text"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:textStyle="normal" />
  30. </LinearLayout>
  31.  
  32. <LinearLayout
  33. android:layout_width="match_parent"
  34. android:layout_height="wrap_content"
  35. android:orientation="horizontal">
  36.  
  37. <Button
  38. android:id="@+id/copy_plain_text"
  39. android:layout_width="wrap_content"
  40. android:layout_height="wrap_content"
  41. android:onClick="pastePlainText"
  42. android:text="M0l" />
  43.  
  44. <TextView
  45. android:id="@+id/plain_text"
  46. android:layout_width="wrap_content"
  47. android:layout_height="wrap_content"
  48. android:textStyle="normal" />
  49. </LinearLayout>
  50.  
  51. <LinearLayout
  52. android:layout_width="match_parent"
  53. android:layout_height="wrap_content"
  54. android:orientation="horizontal">
  55.  
  56. <Button
  57. android:id="@+id/copy_html_text"
  58. android:layout_width="wrap_content"
  59. android:layout_height="wrap_content"
  60. android:onClick="pasteHtmlText"
  61. android:text="复制文本" />
  62.  
  63. <TextView
  64. android:id="@+id/html_text"
  65. android:layout_width="wrap_content"
  66. android:layout_height="wrap_content"
  67. android:textStyle="normal" />
  68. </LinearLayout>
  69.  
  70. <LinearLayout
  71.  
  72. android:layout_width="match_parent"
  73. android:layout_height="wrap_content"
  74. android:orientation="horizontal">
  75.  
  76. <Button
  77. android:id="@+id/copy_intent"
  78. android:layout_width="wrap_content"
  79. android:layout_height="wrap_content"
  80. android:onClick="pasteIntent"
  81. android:text="复制intent" />
  82. </LinearLayout>
  83.  
  84. <LinearLayout
  85. android:layout_width="match_parent"
  86. android:layout_height="wrap_content">
  87.  
  88. <Button
  89. android:id="@+id/copy_uri"
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:onClick="pasteUri"
  93. android:text="复制URI" />
  94. </LinearLayout>
  95.  
  96. <LinearLayout
  97. android:layout_width="match_parent"
  98. android:layout_height="wrap_content"
  99. android:layout_marginTop="8dp"
  100. android:orientation="horizontal">
  101.  
  102. <TextView
  103. android:layout_width="wrap_content"
  104. android:layout_height="wrap_content"
  105. android:text="Data type: "
  106. android:textAppearance="?android:attr/textAppearanceMedium" />
  107.  
  108. <Spinner
  109. android:id="@+id/clip_type"
  110. android:layout_width="wrap_content"
  111. android:layout_height="wrap_content"
  112. android:drawSelectorOnTop="true" />
  113. </LinearLayout>
  114.  
  115. <LinearLayout
  116. android:layout_width="match_parent"
  117. android:layout_height="wrap_content"
  118. android:layout_marginTop="4dp"
  119. android:orientation="horizontal">
  120.  
  121. <TextView
  122. android:layout_width="wrap_content"
  123. android:layout_height="wrap_content"
  124. android:text="MIME types: "
  125. android:textAppearance="?android:attr/textAppearanceMedium" />
  126.  
  127. <TextView
  128. android:id="@+id/clip_mime_types"
  129. android:layout_width="0dp"
  130. android:layout_height="wrap_content"
  131. android:layout_weight="1"
  132. android:background="#ff303030"
  133. android:padding="4dp"
  134. android:textAppearance="?android:attr/textAppearanceMedium" />
  135.  
  136. </LinearLayout>
  137.  
  138. <TextView
  139. android:layout_width="wrap_content"
  140. android:layout_height="wrap_content"
  141. android:layout_marginTop="4dp"
  142. android:text="Data content:"
  143. android:textAppearance="?android:attr/textAppearanceMedium" />
  144.  
  145. <TextView
  146. android:id="@+id/clip_text"
  147. android:layout_width="match_parent"
  148. android:layout_height="wrap_content"
  149. android:background="#ff303030"
  150. android:padding="4dp"
  151. android:textAppearance="?android:attr/textAppearanceMedium" />
  152. </LinearLayout>
  153. </ScrollView>
该实例在定义 Button 的同时直接指定其响应函数,例如:
  1. <Button
  2. android:id="@+id/copy_styled_text"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:onClick="pasteStyledText"
  6. android:text="复制格式文本"/>
其中的 android:onClick="pasteStyledText" 字段表示该按钮被点击时,直接调用 pasteStyledTest 方法进行处理,这样就省去了在 Java 文件中编写响应方法的代码。

该实例在复制过程中用到了一个名为 styled_text 的字符串,用 HTML 标识了文字的加粗、斜体等效果。因此,在工程的 values/strings.xml 文件中加入该变量名对应的字符串如下:

<string name="styled_text">Plain, <b>bold</b>, <i>italic</i>,
<b><i>bold-italic</i></b></string>

布局中的下拉列表在填充数据的过程中使用了数组进行填充,因此在工程的 values 文件夹下新建 Arrays.xml 文件,并新建数组数据如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <string-array name="clip_data_types">
  4. <item>No data in clipboard</item>
  5. <item>Text clip</item>
  6. <item>HTML Text clip</item>
  7. <item>Intent clip</item>
  8. <item>Uri clip</item>
  9. <item>Coerce to text</item>
  10. <item>Coerce to styled text</item>
  11. <item>Coerce to HTML text</item>
  12. </string-array>
  13. </resources>
实例 ClipBoardDemo 的 MainActivity.java 文件代码如下:
  1. package introduction.android.clipboarddemo;
  2.  
  3. import android.app.Activity;
  4. import android.content.ClipboardManager;
  5. import android.content.ClipData;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.res.Resources;
  9. import android.net.Uri;
  10. import android.os.Bundle;
  11. import android.text.method.LinkMovementMethod;
  12. import android.view.View;
  13. import android.widget.AdapterView;
  14. import android.widget.ArrayAdapter;
  15. import android.widget.Spinner;
  16. import android.widget.TextView;
  17. import android.widget.AdapterView.OnItemSelectedListener;
  18.  
  19. public class ClipBoardDemoActivity extends Activity {
  20. ClipboardManager mClipboard;
  21. Spinner mSpinner;
  22. TextView mMimeTypes;
  23. TextView mDataText;
  24. CharSequence mStyledText;
  25. String mPlainText;
  26. String mHtmlText;
  27. String mHtmlPlainText;
  28. ClipboardManager.OnPrimaryClipChangedListener mPrimaryChangeListener = new ClipboardManager.OnPrimaryClipChangedListener() {
  29. public void onPrimaryClipChanged() {
  30. updateClipData(true);
  31. }
  32. };
  33.  
  34. @Override
  35. protected void onCreate(Bundle savedInstanceState) {
  36. super.onCreate(savedInstanceState);
  37.  
  38. mClipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
  39. setContentView(R.layout.main);
  40. TextView tv;
  41. mStyledText = getText(R.string.app_name);
  42. tv = (TextView) findViewById(R.id.styled_text);
  43. tv.setText(mStyledText);
  44. mPlainText = mStyledText.toString();
  45. tv = (TextView) findViewById(R.id.plain_text);
  46. tv.setText(mPlainText);
  47. mHtmlText = "<b>Link:</b> <a href=\"http://www.android.com\">Android</a>";
  48. mHtmlPlainText = "Link: http://www.android.com";
  49. tv = (TextView) findViewById(R.id.html_text);
  50. tv.setText(mHtmlText);
  51. mSpinner = (Spinner) findViewById(R.id.clip_type);
  52.  
  53. ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
  54. R.array.clip_data_types,
  55. android.R.layout.simple_spinner_item);
  56. adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  57. mSpinner.setAdapter(adapter);
  58. mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
  59. public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  60. updateClipData(false);
  61. }
  62.  
  63. public void onNothingSelected(AdapterView<?> parent) {
  64. }
  65. });
  66. mMimeTypes = (TextView) findViewById(R.id.clip_mime_types);
  67. mDataText = (TextView) findViewById(R.id.clip_text);
  68. mClipboard.addPrimaryClipChangedListener(mPrimaryChangeListener);
  69. updateClipData(true);
  70. }
  71.  
  72. @Override
  73. protected void onDestroy() {
  74. super.onDestroy();
  75. mClipboard.removePrimaryClipChangedListener(mPrimaryChangeListener);
  76. }
  77.  
  78. public void pasteStyledText(View button) {
  79. mClipboard.setPrimaryClip(ClipData.newPlainText("Styled Text", mStyledText));
  80. }
  81.  
  82. public void pastePlainText(View button) {
  83. mClipboard.setPrimaryClip(ClipData.newPlainText("Styled Text", mPlainText));
  84. }
  85.  
  86. public void pasteHtmlText(View button) {
  87. mClipboard.setPrimaryClip(ClipData.newHtmlText("HTML Text", mHtmlPlainText,
  88. mHtmlText));
  89. }
  90.  
  91. public void pastelntent(View button) {
  92. Intent intent = new Intent(Intent.ACTION_VIEW,
  93. Uri.parse("http://www.android.com/"));
  94. mClipboard.setPrimaryClip(ClipData.newIntent("VIEW intent", intent));
  95. }
  96.  
  97. public void pasteUri(View button) {
  98. mClipboard.setPrimaryClip(ClipData.newRawUri("URI",
  99. Uri.parse("http://www.android.com/")));
  100. }
  101.  
  102. void updateClipData(boolean updateType) {
  103. ClipData clip = mClipboard.getPrimaryClip();
  104. String[] mimeTypes = clip != null ? clip.getDescription().filterMimeTypes("*/*") :
  105. null;
  106. if (mimeTypes != null) {
  107. mMimeTypes.setText("");
  108. for (int i = 0; i < mimeTypes.length; i++) {
  109. if (i > 0) {
  110. mMimeTypes.append("\n");
  111. mMimeTypes.append(mimeTypes[i]);
  112. }
  113. }
  114. } else {
  115. mMimeTypes.setText("NULL");
  116. }
  117. if (updateType) {
  118. if (clip != null) {
  119. ClipData.Item item = clip.getItemAt(0);
  120. if (item.getHtmlText() != null) {
  121. mSpinner.setSelection(2);
  122. } else if (item.getText() != null) {
  123. mSpinner.setSelection(1);
  124. } else if (item.getIntent() != null) {
  125. mSpinner.setSelection(3);
  126. } else if (item.getUri() != null) {
  127. mSpinner.setSelection(4);
  128. } else {
  129. mSpinner.setSelection(0);
  130. }
  131. } else {
  132. mSpinner.setSelection(0);
  133. }
  134. }
  135. if (clip != null) {
  136. ClipData.Item item = clip.getItemAt(0);
  137. switch (mSpinner.getSelectedItemPosition()) {
  138. case 0:
  139. mDataText.setText("(No data)");
  140. break;
  141. case 1:
  142. mDataText.setText(item.getText());
  143. break;
  144. case 2:
  145. mDataText.setText(item.getHtmlText());
  146. break;
  147. case 3:
  148. mDataText.setText(item.getIntent().toUri(0));
  149. break;
  150. case 4:
  151. mDataText.setText(item.getUri().toString());
  152. break;
  153. case 5:
  154. mDataText.setText(item.coerceToText(this));
  155. break;
  156. case 6:
  157. mDataText.setText(item.coerceToStyledText(this));
  158. break;
  159. case 7:
  160. mDataText.setText(item.coerceToHtmlText(this));
  161. break;
  162. default:
  163. mDataText.setText("Unknown option: " + mSpinner.getSelectedItemPosition());
  164. break;
  165. }
  166. } else {
  167. mDataText.setText("(NULL clip)");
  168. }
  169. mDataText.setMovementMethod(LinkMovementMethod.getInstance());
  170. }
  171. }
其中代码段:
  1. if (clip != null) {
  2. ClipData.Item item = clip.getItemAt(0);
  3. if (item.getHtmlText() != null) {
  4. mSpinner.setSelection(2);
  5. } else if (item.getText() != null) {
  6. mSpinner.setSelection(1);
  7. } else if (item.getIntent() != null) {
  8. mSpinner.setSelection(3);
  9. } else if (item.getUri() != null) {
  10. mSpinner.setSelection(4);
  11. } else {
  12. mSpinner.setSelection(0);
  13. }
  14. } else {
  15. mSpinner.setSelection(0);
  16. }
是根据粘贴内容来确定下拉列表的显示项的。

代码段:
  1. ClipData.Item item = clip.getItemAt(0);
  2. switch (mSpinner.getSelectedItemPosition()) {
  3. case 0:
  4. mDataText.setText("(No data)");
  5. break;
  6. case 1:
  7. mDataText.setText(item.getText());
  8. break;
  9. case 2:
  10. mDataText.setText(item.getHtmlText());
  11. break;
  12. case 3:
  13. mDataText.setText(item.getIntent().toUri(0));
  14. break;
  15. case 4:
  16. mDataText.setText(item.getUri().toString());
  17. break;
  18. case 5:
  19. mDataText.setText(item.coerceToText(this));
  20. break;
  21. case 6:
  22. mDataText.setText(item.coerceToStyledText(this));
  23. break;
  24. case 7:
  25. mDataText.setText(item.coerceToHtmlText(this));
  26. break;
  27. default:
  28. mDataText.setText("Unknown option: " + mSpinner.getSelectedItemPosition());
  29. break;
  30. }
是根据用户选择的下拉列表项将粘贴内容转化为相应字符串并显示出来的。