Android復制TextView內容常用方法匯總
最近在項目中,需要提供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內容常用方法匯總/