Android剪切板

介绍

Android提供了一个强大的基于剪贴板的复制和粘贴框架。它既支持简单的数据类型,也支持复杂的数据类型,包括文本字符串、复杂的数据结构、文本和二进制流数据,甚至还支持应用程序资源。

如下图所示:


clip.png

由上图可以简单的得到Android剪切板模版主要由四个类构成:ClipboardManagerClipDataClipData.ItemClipDescription.

简单的描述:系统复制数据,就是创建一个ClipData对象放在ClipboardManager全局上.ClipData可以包括多条Item子数据,子数据中复制内容可以是text,url,intent,但是都是这些子数据都是来自一次复制,每次复制会覆盖之前的复制内容.同时,ClipData中包含一个ClipDescription,用于描述本次复制内容的MimeType.

核心类

  • ClipboardManager

系统服务全局的剪切板类.如何得到如下:

ClipboardManager mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);

定义当剪贴板上的主剪辑发生更改时调用的侦听器回调:OnPrimaryClipChangedListener.

// 添加剪贴板数据改变监听器
mClipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
    @Override
    public void onPrimaryClipChanged() {
        // 剪贴板中的数据被改变,此方法将被回调
        System.out.println("onPrimaryClipChanged()");
    }
});

// 移除指定的剪贴板数据改变监听器
 mClipboardManager.removePrimaryClipChangedListener(listener);
  • ClipData.Item
    剪切板子数据类,它包含了texthtmlUri或者Intent数据,一个clip对象可以包含一个或多个Item对象。
    一起来看看它的属性:
        final CharSequence mText;
        final String mHtmlText;
        final Intent mIntent;
        Uri mUri;

就是一个数据类.

  • ClipDescription
    剪切板的描述类.包含了ClipData对象的metadata信息,一般情况mimeType只有一个.
    一起看看它的属性就知道干什么的类了.
public class ClipDescription implements Parcelable {
    //默认的MimeTYpe
    public static final String MIMETYPE_TEXT_PLAIN = "text/plain";

    public static final String MIMETYPE_TEXT_HTML = "text/html";
    
    public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";

    public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";

    public static final String EXTRA_TARGET_COMPONENT_NAME =
            "android.content.extra.TARGET_COMPONENT_NAME";
    public static final String EXTRA_USER_SERIAL_NUMBER =    "android.content.extra.USER_SERIAL_NUMBER";
    //包含一个标签
    final CharSequence mLabel;
    //mimeType数组
    final String[] mMimeTypes;
    //可以保存额外的数据
    private PersistableBundle mExtras;
    ......
    
}

一般使用前面四种:text、html、uri、intent.其中url比较特殊.如果使用Android资源MimeType需要由ContentResolver提供.
什么是uri:

通用资源标志符(Universal Resource Identifier, 简称"URI")。
Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示。
Android的Uri由以下三部分组成: "content://"、数据的路径、标示ID(可选)

  • ClipData
    剪切对象,在有且仅有一个剪切板对象在系统服务中.言外之意,每一次复制前一次复制内容都会消失.
    一起来看看它的属性:
public class ClipData implements Parcelable {
    //默认mimetype。 text/plain
    static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
        ClipDescription.MIMETYPE_TEXT_PLAIN };
    //text/html
    static final String[] MIMETYPES_TEXT_HTML = new String[] {
        ClipDescription.MIMETYPE_TEXT_HTML };
    //urllist
    static final String[] MIMETYPES_TEXT_URILIST = new String[] {
        ClipDescription.MIMETYPE_TEXT_URILIST };
    //intent
    static final String[] MIMETYPES_TEXT_INTENT = new String[] {
        ClipDescription.MIMETYPE_TEXT_INTENT };
    
    //剪切板描述类
    final ClipDescription mClipDescription;
    
    final Bitmap mIcon;
    
    //用于存放剪切板子数据
    final ArrayList<Item> mItems;

    .......
}

创建方式:

/**
     * Create a new ClipData holding data of the type
     * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
     *
     * @param label User-visible label for the clip data.
     * @param text The actual text in the clip.
     * @return Returns a new ClipData containing the specified data.
     */
    static public ClipData newPlainText(CharSequence label, CharSequence text) {
        Item item = new Item(text);
        return new ClipData(label, MIMETYPES_TEXT_PLAIN, item);
    }

    /**
     * Create a new ClipData holding data of the type
     * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
     *
     * @param label User-visible label for the clip data.
     * @param text The text of clip as plain text, for receivers that don't
     * handle HTML.  This is required.
     * @param htmlText The actual HTML text in the clip.
     * @return Returns a new ClipData containing the specified data.
     */
    static public ClipData newHtmlText(CharSequence label, CharSequence text,
            String htmlText) {
        Item item = new Item(text, htmlText);
        return new ClipData(label, MIMETYPES_TEXT_HTML, item);
    }

    /**
     * Create a new ClipData holding an Intent with MIME type
     * {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
     *
     * @param label User-visible label for the clip data.
     * @param intent The actual Intent in the clip.
     * @return Returns a new ClipData containing the specified data.
     */
    static public ClipData newIntent(CharSequence label, Intent intent) {
        Item item = new Item(intent);
        return new ClipData(label, MIMETYPES_TEXT_INTENT, item);
    }

    /**
     * Create a new ClipData holding a URI.  If the URI is a content: URI,
     * this will query the content provider for the MIME type of its data and
     * use that as the MIME type.  Otherwise, it will use the MIME type
     * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
     *
     * @param resolver ContentResolver used to get information about the URI.
     * @param label User-visible label for the clip data.
     * @param uri The URI in the clip.
     * @return Returns a new ClipData containing the specified data.
     */
    static public ClipData newUri(ContentResolver resolver, CharSequence label,
            Uri uri) {
        //创建item
        Item item = new Item(uri);
        /*获取mimeType*/
        String[] mimeTypes = null;
        if ("content".equals(uri.getScheme())) {
            String realType = resolver.getType(uri);
            mimeTypes = resolver.getStreamTypes(uri, "*/*");
            if (realType != null) {
                if (mimeTypes == null) {
                    mimeTypes = new String[] { realType };
                } else {
                    String[] tmp = new String[mimeTypes.length + 1];
                    tmp[0] = realType;
                    System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length);
                    mimeTypes = tmp;
                }
            }
        }
        if (mimeTypes == null) {
            mimeTypes = MIMETYPES_TEXT_URILIST;
        }
        return new ClipData(label, mimeTypes, item);
    }

    /**
     * Create a new ClipData holding an URI with MIME type
     * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
     * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing
     * is inferred about the URI -- if it is a content: URI holding a bitmap,
     * the reported type will still be uri-list.  Use this with care!
     *
     * @param label User-visible label for the clip data.
     * @param uri The URI in the clip.
     * @return Returns a new ClipData containing the specified data.
     */
    static public ClipData newRawUri(CharSequence label, Uri uri) {
        //创建item
        Item item = new Item(uri);
        return new ClipData(label, MIMETYPES_TEXT_URILIST, item);
    }

clipData对象创建后塞入Clipboardmanager即可:

//Clipboardmanager方法
/**
     * Sets the current primary clip on the clipboard.  This is the clip that
     * is involved in normal cut and paste operations.
     *
     * @param clip The clipped data item to set.
     */
    public void setPrimaryClip(ClipData clip) {
        try {
            if (clip != null) {
                clip.prepareToLeaveProcess(true);
            }
            getService().setPrimaryClip(clip, mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

  • 转换成字符串
//任何作为HTML格式返回的文本都将作为具有样式跨度的文本返回。
CharSequence coerceToStyledText(Context context);

//如果getText()是非空的,则返回该值。
//如果getUri()非null,则尝试从其内容提供程序检索其数据作为文本流。如果成功,将文本复制到字符串中并返回。如果它不是内容:URI或内容提供程序不提供文本表示,则将原始URI作为字符串返回。
//如果getIntent()非null,则将其转换为intent: URI并返回。
//否则,返回一个空字符串。
CharSequence coerceToText(Context context) ;

//如果getHtmlText()非null,则返回该值。
//如果getText()是非空的,返回它,转换为有效的HTML文本。如果此文本包含样式跨度,则使用HTML . tohtml (span)将其转换为HTML格式。
//如果getUri()非null,则尝试从其内容提供程序检索其数据作为文本流。
//如果提供程序可以提供文本/html数据,则首选该数据并按原样返回。否则,将返回任何文本/*数据并转义到HTML。
//如果它不是内容:URI或内容提供程序不提供文本表示,将返回包含到URI链接的HTML文本。
//如果getIntent()非null,则将其转换为intent: URI并以HTML链接的形式返回。
//否则,返回一个空字符串。
String coerceToHtmlText(Context context) 

详细的内容可以查看官网,地址我也写出来了,在最下面,哈哈哈.

  • 注意
    1、剪切板只会保存最近一次复制的内容.
    2、MimeType一般只有一个.(可以有多个)
    3、系统全局的剪切板,其他应用也可以使用.

工具类


package com.rnx.react.modules.clip;

import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;

import java.util.List;

/**
 * @Auther: weiwei.zhang06
 * @Date: 2018/12/5 18:59
 */

public class ClipboardHelper {

  public static final String TAG = ClipboardHelper.class.getSimpleName();

  private Context mContext;
  private volatile static ClipboardHelper mInstance;
  private ClipboardManager mClipboardManager;

  private ClipboardHelper(Context context) {
    mContext = context;
    mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
  }

  /**
   * 获取ClipboardUtil实例,记得初始化
   *
   * @return 单例
   */
  public static ClipboardHelper getInstance(Context context) {
    if (mInstance == null) {
      synchronized (ClipboardHelper.class) {
        if (mInstance == null) {
          mInstance = new ClipboardHelper(context.getApplicationContext());
        }
      }
    }
    return mInstance;
  }

  /**
   * 判断剪贴板内是否有数据
   *
   * @return
   */
  public boolean hasPrimaryClip() {
    return mClipboardManager.hasPrimaryClip();
  }

  /**
   * 获取剪贴板中第一条String
   *
   * @return
   */
  public String getClipText() {
    if (!hasPrimaryClip()) {
      return null;
    }
    ClipData data = mClipboardManager.getPrimaryClip();
    if (data != null
      && mClipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
      return data.getItemAt(0).getText().toString();
    }
    return null;
  }

  /**
   * 获取剪贴板中第一条String
   *
   * @param context
   * @return
   */
  public String getClipText(Context context) {
    return getClipText(context, 0);
  }

  /**
   * 获取剪贴板中指定位置item的string
   *
   * @param context
   * @param index
   * @return
   */
  public String getClipText(Context context, int index) {
    if (!hasPrimaryClip()) {
      return null;
    }
    ClipData data = mClipboardManager.getPrimaryClip();
    if (data == null) {
      return null;
    }
    if (data.getItemCount() > index) {
      return data.getItemAt(index).coerceToText(context).toString();
    }
    return null;
  }

  /**
   * 将文本拷贝至剪贴板
   *
   * @param text
   */
  public void copyText(String label, String text) {
    ClipData clip = ClipData.newPlainText(label, text);
    mClipboardManager.setPrimaryClip(clip);
  }

  /**
   * 将HTML等富文本拷贝至剪贴板
   *
   * @param label
   * @param text
   * @param htmlText
   */
  public void copyHtmlText(String label, String text, String htmlText) {
    ClipData clip = ClipData.newHtmlText(label, text, htmlText);
    mClipboardManager.setPrimaryClip(clip);
  }

  /**
   * 将Intent拷贝至剪贴板
   *
   * @param label
   * @param intent
   */
  public void copyIntent(String label, Intent intent) {
    ClipData clip = ClipData.newIntent(label, intent);
    mClipboardManager.setPrimaryClip(clip);
  }

  /**
   * 将Uri拷贝至剪贴板
   * If the URI is a content: URI,
   * this will query the content provider for the MIME type of its data and
   * use that as the MIME type.  Otherwise, it will use the MIME type
   * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
   * 如 uri = "content://contacts/people",那么返回的MIME type将变成"vnd.android.cursor.dir/person"
   *
   * @param cr    ContentResolver used to get information about the URI.
   * @param label User-visible label for the clip data.
   * @param uri   The URI in the clip.
   */
  public void copyUri(ContentResolver cr, String label, Uri uri) {
    ClipData clip = ClipData.newUri(cr, label, uri);
    mClipboardManager.setPrimaryClip(clip);
  }

  /**
   * 将多组数据放入剪贴板中,如选中ListView多个Item,并将Item的数据一起放入剪贴板
   *
   * @param label    User-visible label for the clip data.
   * @param mimeType mimeType is one of them:{@link ClipDescription#MIMETYPE_TEXT_PLAIN},
   *                 {@link ClipDescription#MIMETYPE_TEXT_HTML},
   *                 {@link ClipDescription#MIMETYPE_TEXT_URILIST},
   *                 {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
   * @param items    放入剪贴板中的数据
   */
  public void copyMultiple(String label, String mimeType, List<ClipData.Item> items) {
    if (items == null || items.size() == 0) {
      throw new IllegalArgumentException("argument: items error");
    }
    int size = items.size();
    ClipData clip = new ClipData(label, new String[]{mimeType}, items.get(0));
    for (int i = 1; i < size; i++) {
      clip.addItem(items.get(i));
    }
    mClipboardManager.setPrimaryClip(clip);
  }

  public void copyMultiple(String label, String[] mimeTypes, List<ClipData.Item> items) {
    if (items == null || items.size() == 0) {
      throw new IllegalArgumentException("argument: items error");
    }
    int size = items.size();
    ClipData clip = new ClipData(label, mimeTypes, items.get(0));
    for (int i = 1; i < size; i++) {
      clip.addItem(items.get(i));
    }
    mClipboardManager.setPrimaryClip(clip);
  }

  public CharSequence coercePrimaryClipToText() {
    if (!hasPrimaryClip()) {
      return null;
    }
    return mClipboardManager.getPrimaryClip().getItemAt(0).coerceToText(mContext);
  }

  public CharSequence coercePrimaryClipToStyledText() {
    if (!hasPrimaryClip()) {
      return null;
    }
    return mClipboardManager.getPrimaryClip().getItemAt(0).coerceToStyledText(mContext);
  }

  public CharSequence coercePrimaryClipToHtmlText() {
    if (!hasPrimaryClip()) {
      return null;
    }
    return mClipboardManager.getPrimaryClip().getItemAt(0).coerceToHtmlText(mContext);
  }

  /**
   * 获取当前剪贴板内容的MimeType
   *
   * @return 当前剪贴板内容的MimeType
   */
  public String getPrimaryClipMimeType() {
    if (!hasPrimaryClip()) {
      return null;
    }
    return mClipboardManager.getPrimaryClipDescription().getMimeType(0);
  }

  /**
   * 获取剪贴板内容的MimeType
   *
   * @param clip 剪贴板内容
   * @return 剪贴板内容的MimeType
   */
  public String getClipMimeType(ClipData clip) {
    return clip.getDescription().getMimeType(0);
  }

  /**
   * 获取剪贴板内容的MimeType
   *
   * @param clipDescription 剪贴板内容描述
   * @return 剪贴板内容的MimeType
   */
  public String getClipMimeType(ClipDescription clipDescription) {
    return clipDescription.getMimeType(0);
  }

  /**
   * 清空剪贴板
   */
  public void clearClip() {
    mClipboardManager.setPrimaryClip(ClipData.newPlainText(null, ""));
  }

  public ClipData getClipData() {
    if (!hasPrimaryClip()) {
      return null;
    }
    return mClipboardManager.getPrimaryClip();
  }
}


官网: https://developer.android.com/guide/topics/text/copy-paste#java

ClipboardManger api: https://developer.android.com/reference/android/content/ClipboardManager

ClipData api: https://developer.android.com/reference/android/content/ClipData

ClipData.item api: https://developer.android.com/reference/android/content/ClipData.Item

ClipDescription api: https://developer.android.com/reference/android/content/ClipDescription


感谢阅读

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容