Android復制TextView內容常用方法匯總

Ngogo58 7年前發布 | 18K 次閱讀 TextView Android開發 移動開發

最近在項目中,需要提供TextView的復制功能,讓用戶可以自由復制App中某些界面的特定內容。

需求來源于用戶有時需要復制編號,用于其它地方的搜索,比如下圖中TD開頭的這一長串編號。

如果在搜索時,需要手輸這一長串字符,先不說記住這一串數據需要怎樣的記憶力,就說輸入這一長串字符的時間,對于用戶來說就是不可接受的。

二、需求分析

這時候,我們很自然地就會想到,我們經常使用的復制功能,是類似這樣的:

長按某一段文字后,在文字上方或標題欄處會出現 全選 、 復制 等功能按鈕,點擊后就能復制,然后在你需要輸入的地方長按,就會出現 粘貼 按鈕,點擊后就能成功粘貼。

三、功能實現

查看 TextView的API ,里面就有這么一段介紹:

To allow users to copy some or all of the TextView’s value and paste it somewhere else, set the XML attribute android:textIsSelectable to “ true “ or call setTextIsSelectable(true) . The textIsSelectable flag allows users to make selection gestures in the TextView, which in turn triggers the system’s built-in copy/paste controls.

意思就是使用 textIsSelectable 屬性就可以觸發系統內置的復制/粘貼功能。

那么最簡單的方法就是直接在XML文件中,給TextView設置 textIsSelectable 屬性為 true :

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="TD201612120166"
    android:textIsSelectable="true" />

當然,也可以使用java代碼實現:

tv.setTextIsSelectable(true);

效果也確實立桿見影,一步完成。

好嗨森,可以收工啦,趕緊把項目中另一個地方也改掉,就可以提交代碼啦~

四、掉坑了

由于本項目為老項目,列表使用的是ListView(SDK使用的是19),而另一個需要修改的地方,就是ListView列表里面的子項TextView。

當為其設置 textIsSelectable 屬性后,長按這一長串數字編號,這時候數字是選中了,但是并沒有彈出 復制 的選項啊,而且更重要的是, ListView的Item點擊事件沒有了啊啊啊~~~

注:在某些SDK版本上,長按TextView后,會在ToolBar(ActionBar)位置彈出 全選 、 復制 等功能按鈕(如下圖),而當設置 textIsSelectable 屬性后,該功能條會下滑出現,而在一瞬間又上滑消失,實際看到的效果就是ToolBar處會有內容閃現并瞬間消失,但是看不清具體內容。

五、解決

網上搜索一番,果然也有不少解決方案,但有一些親測無效,就不列出了,以下是親測可用的解決方案。

以下方法,基本都是使用系統的剪貼板管理器 ClipboardManager 進行內容的復制。

1. LongClick事件 + PopupWindow

原理是在TextView的長按事件中,顯示一個帶操作按鈕的PopupWindow,點擊其中的 復制 按鈕后,復制TextView內容。

先初始化PopupWindow,彈出窗口上會有復制按鈕(也可以自定義其它需要的按鈕):

private void initPopupWindow() {
    View popupView = getLayoutInflater().inflate(R.layout.layout_popup_window, null);

    mPopupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
    mPopupWindow.setTouchable(true);
    mPopupWindow.setOutsideTouchable(true);
    mPopupWindow.setBackgroundDrawable(new BitmapDrawable(getResources(), (Bitmap) null));

    Button btnCopy = (Button) popupView.findViewById(R.id.btn_copy);
}

然后為ListView中的TextView設置長按事件,在TextView下方顯示PopupWindow:

mTextView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        // 方法1:LongClick事件 + PopupWindow
        mPopupWindow.showAsDropDown(view);
        mCopiedText = ((TextView) view).getText().toString();

        return false;

    }
});

最后為彈出窗口中的復制按鈕設置響應事件,利用ClipboardManager復制TextView內容:

btnCopy.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        copyText(mCopiedText);

        if (mPopupWindow != null && mPopupWindow.isShowing()) {
            mPopupWindow.dismiss();
        }
    }
});

private void copyText(String copiedText) {
    ClipboardManager clipboardManager = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
    clipboardManager.setPrimaryClip(ClipData.newPlainText(null, copiedText));

    showToast("Copy Text: " + copiedText);
}

以上就可以在你需要輸入的地方長按,就會出現 粘貼 按鈕,點擊后就能成功粘貼剛才復制的內容了。

但是這邊會有個問題:無法自由選擇文字內容,沒有選中文字的效果

2. LongClick事件 + ContextMenu

我們也可以使用 ContextMenu 來手動創建上下文菜單,實現跟原生復制功能一樣的效果,在標題欄上方顯示可操作的菜單,最終效果如下:

首先,實現 ActionMode.Callback 接口。這里面會包含ActionMode的生命周期方法,在 onCreateActionMode() 中 創建菜單 項,如復制、全選等功能菜單,在 onActionItemClicked() 中處理菜單項的 點擊事件 。 (重點代碼用下三角▼▼▼標識了)

這兩個方法就類似標題欄右邊的選項菜單 OptionsMenu ,對應于 onCreateOptionsMenu() 和 onOptionsItemSelected() 這兩個方法。

private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();

        // ▼▼▼重點:創建上下文菜單
        inflater.inflate(R.menu.context_menu, menu);
        return true;
    }

    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }

    ...

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
    }
};

然后,為TextView設置長按事件,顯示上下文菜單:

mTextView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        // 方案2:LongClick事件 + ContextMenu
        if (mActionMode != null) {
            return false;
        }

        // Start the CAB using the ActionMode.Callback defined above
        mActionMode = startActionMode(mActionModeCallback);
        view.setSelected(true);
        mCopiedText = ((TextView) view).getText().toString();
        return true;
    }
});

最后,就是在第一步的onActionItemClicked()事件中,為 復制 按鈕設置響應事件,利用ClipboardManager復制TextView內容:

private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    ...

    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            // ▼▼▼重點:設置菜單項的點擊事件
            case R.id.action_copy:
                copyText(mCopiedText);

                mode.finish(); // Action picked, so close the CAB
                return true;
            default:
                return false;
        }
    }
};

這邊同樣會有上面的問題:無法自由選擇文字內容,沒有選中文字的效果

3. Click事件 + ClipboardManager

此方案跟第1種原理是一樣的,只不過省略了PopupWindow部分,點擊后直接自動復制TextView內容,代碼比較簡單:

mTextView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        copyText(((TextView) view).getText().toString());
    }
});

問題:無法自由選擇文字內容

4. RecyclerView + textIsSelectable

其實最推薦的做法,還是將項目里的ListView替換成 RecyclerView ,再給TextView設置 textIsSelectable=true 屬性即可:

這讓我們再一次見證了RecyclerView的偉大啊~

本方案對項目后期維護及擴展更有好處,畢竟現在基本可以說,RecyclerView已經完全取代了ListView,比ListView性能更好,擴展性更強。

5. 額外贈送:SelectableTextHelper

網上還有位大神自定義了可選擇TextView內容的幫助類,使用上非常方便,只要對原來的TextView加上如下設置即可:

mSelectableTextHelper = new SelectableTextHelper.Builder(mTextView)
    .setSelectedColor(getResources().getColor(R.color.selected_blue))
    .setCursorHandleSizeInDp(20)
    .setCursorHandleColor(getResources().getColor(R.color.cursor_handle_color))
    .build();

親測在文字選擇時,左右兩邊選擇文字范圍的圖標不好點中,應該是坐標處理有點問題;而且無法在ListView中使用;但不得不說,整體效果非常好,對代碼的侵入非常少,在各個平臺上的體驗也是統一的。

六、總結

這幾種方案來看的話:

  • 如果項目中需要復制的TextView沒有嵌套在ListView中,那就可以直接大膽地使用 textIsSelectable 屬性,一步搞定;
  • 如果使用到ListView,能改成RecyclerView來實現的話,建議直接改成RecyclerView,再加上 textIsSelectable 屬性,也很好解決;(方案4)
  • 如果非要用ListView的話,看看是否能在滿足項目需求的情況下,直接使用TextView的點擊事件,配合 ClipboardManager 直接自動復制指定內容,但記得在復制完成后給用戶顯示必要的提示。(方案3)========> 本項目就是使用這種方案簡單粗暴地解決ListView中無法長按選擇復制TextView內容的問題的。
  • 如果這幾種方案還不能解決你的問題,可以在其余幾種方案中看看是否有符合要求的。

七、疑問

雖然項目問題解決了,但是還有以下幾個問題,在查看了相關源碼、進行一系列測試后,還是未能解決,暫且記錄下來,說不定以后什么時候能力達到了,就能解決了呢?或者哪位小伙伴知道原因,還請不吝賜教~

  • 為什么設置 textIsSelectable 為 true 后,ListView的ItemClick事件不響應了,明明該有的DOWN、MOVE、UP事件還是有?ItemClick的觸發條件是什么?
  • 而這時候,為什么TextView不會彈出選中框,以及復制、全選等操作按鈕?而在添加了 android:selectAllOnFocus="true" 后,長按后顯示紅色選擇框,但不顯示復制按鈕,再次點擊紅色選擇框,就會出現復制按鈕。
  • TextView長按后,在LongClick事件之后,就不再有Touch事件產生(如附錄最后一項測試結果,以下附錄只是記錄而已)

附錄

1. 當 textIsSelectable 為 false 時

1.1 未設置TextView的點擊、長按事件:

點擊ListView:

ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

點擊TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

長按TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

1.2 有設置TextView的點擊、長按事件:

點擊ListView:

ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

點擊TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↑↑↑ACTION_UP↑↑↑
TextView: ↓onClick↓

長按TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↓↓onLongClick↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↑↑↑ACTION_UP↑↑↑
TextView: ↓onClick↓

2. 當 textIsSelectable 為 true 時

2.1 未設置TextView的點擊、長按事件:

點擊ListView:(ItemClick事件未響應)

ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

點擊TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↑↑↑ACTION_UP↑↑↑

長按TextView:(顯示出紅色選中框后,不再有事件產生)

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: →→→ACTION_MOVE→→→

2.2 有設置TextView的點擊、長按事件:

點擊ListView:

ListView: ↓↓↓ACTION_DOWN↓↓↓
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
ListView: →→→ACTION_MOVE→→→
...
ListView: ↑↑↑ACTION_UP↑↑↑

點擊TextView:

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↑↑↑ACTION_UP↑↑↑
TextView: ↓onClick↓

長按TextView:(onLongClick事件產生后,又產生了一次MOVE事件,然后不再有事件產生)

TextView: ↓↓↓ACTION_DOWN↓↓↓
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
TextView: →→→ACTION_MOVE→→→
...
TextView: ↓↓onLongClick↓↓
TextView: →→→ACTION_MOVE→→→

 

 

來自:http://sherlockshi.github.io/2016/12/19/15_Android/1590_Others/Android復制TextView內容常用方法匯總/

 

 本文由用戶 Ngogo58 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!