一、前言
最近在項(xiàng)目中荣病,需要提供TextView的復(fù)制功能,讓用戶可以自由復(fù)制App中某些界面的特定內(nèi)容旬痹。
需求來(lái)源于用戶有時(shí)需要復(fù)制編號(hào)玄帕,用于其它地方的搜索部脚,比如下圖中TD開頭的這一長(zhǎng)串編號(hào)。
如果在搜索時(shí)裤纹,需要手輸這一長(zhǎng)串字符委刘,先不說(shuō)記住這一串?dāng)?shù)據(jù)需要怎樣的記憶力,就說(shuō)輸入這一長(zhǎng)串字符的時(shí)間鹰椒,對(duì)于用戶來(lái)說(shuō)就是不可接受的锡移。
二、需求分析
這時(shí)候漆际,我們很自然地就會(huì)想到淆珊,我們經(jīng)常使用的復(fù)制功能,是類似這樣的:
長(zhǎng)按某一段文字后奸汇,在文字上方或標(biāo)題欄處會(huì)出現(xiàn)全選
施符、復(fù)制
等功能按鈕往声,點(diǎn)擊后就能復(fù)制,然后在你需要輸入的地方長(zhǎng)按戳吝,就會(huì)出現(xiàn)粘貼
按鈕浩销,點(diǎn)擊后就能成功粘貼。
三听哭、功能實(shí)現(xiàn)
查看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 callsetTextIsSelectable(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
屬性就可以觸發(fā)系統(tǒng)內(nèi)置的復(fù)制/粘貼功能。
那么最簡(jiǎn)單的方法就是直接在XML文件中陆盘,給TextView設(shè)置textIsSelectable
屬性為true
:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TD201612120166"
android:textIsSelectable="true" />
當(dāng)然普筹,也可以使用java代碼實(shí)現(xiàn):
tv.setTextIsSelectable(true);
效果也確實(shí)立桿見影,一步完成隘马。
好嗨森太防,可以收工啦,趕緊把項(xiàng)目中另一個(gè)地方也改掉祟霍,就可以提交代碼啦~
四杏头、掉坑了
由于本項(xiàng)目為老項(xiàng)目,列表使用的是ListView(SDK使用的是19)沸呐,而另一個(gè)需要修改的地方,就是ListView列表里面的子項(xiàng)TextView呢燥。
當(dāng)為其設(shè)置textIsSelectable
屬性后崭添,長(zhǎng)按這一長(zhǎng)串?dāng)?shù)字編號(hào),這時(shí)候數(shù)字是選中了叛氨,但是并沒(méi)有彈出復(fù)制
的選項(xiàng)啊呼渣,而且更重要的是,ListView的Item點(diǎn)擊事件沒(méi)有了啊啊啊~~~
注:在某些SDK版本上寞埠,長(zhǎng)按TextView后屁置,會(huì)在ToolBar(ActionBar)位置彈出
全選
、復(fù)制
等功能按鈕(如下圖)仁连,而當(dāng)設(shè)置textIsSelectable
屬性后蓝角,該功能條會(huì)下滑出現(xiàn),而在一瞬間又上滑消失饭冬,實(shí)際看到的效果就是ToolBar處會(huì)有內(nèi)容閃現(xiàn)并瞬間消失使鹅,但是看不清具體內(nèi)容。
五昌抠、解決
網(wǎng)上搜索一番患朱,果然也有不少解決方案,但有一些親測(cè)無(wú)效炊苫,就不列出了裁厅,以下是親測(cè)可用的解決方案冰沙。
以下方法,基本都是使用系統(tǒng)的剪貼板管理器ClipboardManager
進(jìn)行內(nèi)容的復(fù)制执虹。
1. LongClick事件 + PopupWindow
原理是在TextView的長(zhǎng)按事件中倦淀,顯示一個(gè)帶操作按鈕的PopupWindow,點(diǎn)擊其中的復(fù)制
按鈕后声畏,復(fù)制TextView內(nèi)容撞叽。
先初始化PopupWindow,彈出窗口上會(huì)有復(fù)制按鈕(也可以自定義其它需要的按鈕):
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);
}
然后為L(zhǎng)istView中的TextView設(shè)置長(zhǎng)按事件插龄,在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;
}
});
最后為彈出窗口中的復(fù)制按鈕設(shè)置響應(yīng)事件愿棋,利用ClipboardManager復(fù)制TextView內(nèi)容:
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);
}
以上就可以在你需要輸入的地方長(zhǎng)按,就會(huì)出現(xiàn)粘貼
按鈕均牢,點(diǎn)擊后就能成功粘貼剛才復(fù)制的內(nèi)容了糠雨。
但是這邊會(huì)有個(gè)問(wèn)題:無(wú)法自由選擇文字內(nèi)容,沒(méi)有選中文字的效果
2. LongClick事件 + ContextMenu
我們也可以使用ContextMenu來(lái)手動(dòng)創(chuàng)建上下文菜單徘跪,實(shí)現(xiàn)跟原生復(fù)制功能一樣的效果甘邀,在標(biāo)題欄上方顯示可操作的菜單,最終效果如下:
首先垮庐,實(shí)現(xiàn) ActionMode.Callback
接口松邪。這里面會(huì)包含ActionMode的生命周期方法,在onCreateActionMode()
中創(chuàng)建菜單
項(xiàng)哨查,如復(fù)制逗抑、全選等功能菜單,在onActionItemClicked()
中處理菜單項(xiàng)的點(diǎn)擊事件
寒亥。(重點(diǎn)代碼用下三角▼▼▼標(biāo)識(shí)了)
這兩個(gè)方法就類似標(biāo)題欄右邊的選項(xiàng)菜單
OptionsMenu
邮府,對(duì)應(yīng)于onCreateOptionsMenu()
和onOptionsItemSelected()
這兩個(gè)方法。
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();
// ▼▼▼重點(diǎn):創(chuàng)建上下文菜單
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設(shè)置長(zhǎng)按事件褂傀,顯示上下文菜單:
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()事件中加勤,為復(fù)制
按鈕設(shè)置響應(yīng)事件仙辟,利用ClipboardManager復(fù)制TextView內(nèi)容:
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
...
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
// ▼▼▼重點(diǎn):設(shè)置菜單項(xiàng)的點(diǎn)擊事件
case R.id.action_copy:
copyText(mCopiedText);
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
};
這邊同樣會(huì)有上面的問(wèn)題:無(wú)法自由選擇文字內(nèi)容,沒(méi)有選中文字的效果
3. Click事件 + ClipboardManager
此方案跟第1種原理是一樣的胸竞,只不過(guò)省略了PopupWindow部分欺嗤,點(diǎn)擊后直接自動(dòng)復(fù)制TextView內(nèi)容,代碼比較簡(jiǎn)單:
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
copyText(((TextView) view).getText().toString());
}
});
問(wèn)題:無(wú)法自由選擇文字內(nèi)容
4. RecyclerView + textIsSelectable
其實(shí)最推薦的做法卫枝,還是將項(xiàng)目里的ListView替換成RecyclerView
煎饼,再給TextView設(shè)置textIsSelectable=true
屬性即可:
這讓我們?cè)僖淮我娮C了RecyclerView的偉大啊~
本方案對(duì)項(xiàng)目后期維護(hù)及擴(kuò)展更有好處,畢竟現(xiàn)在基本可以說(shuō)校赤,RecyclerView已經(jīng)完全取代了ListView吆玖,比ListView性能更好筒溃,擴(kuò)展性更強(qiáng)。
5. 額外贈(zèng)送:SelectableTextHelper
網(wǎng)上還有位大神自定義了可選擇TextView內(nèi)容的幫助類沾乘,使用上非常方便怜奖,只要對(duì)原來(lái)的TextView加上如下設(shè)置即可:
mSelectableTextHelper = new SelectableTextHelper.Builder(mTextView)
.setSelectedColor(getResources().getColor(R.color.selected_blue))
.setCursorHandleSizeInDp(20)
.setCursorHandleColor(getResources().getColor(R.color.cursor_handle_color))
.build();
有興趣的同學(xué)可以參考此文章了解更多:自定義選擇復(fù)制功能的實(shí)現(xiàn)
親測(cè)在文字選擇時(shí),左右兩邊選擇文字范圍的圖標(biāo)不好點(diǎn)中翅阵,應(yīng)該是坐標(biāo)處理有點(diǎn)問(wèn)題歪玲;而且無(wú)法在ListView中使用;但不得不說(shuō)掷匠,整體效果非常好滥崩,對(duì)代碼的侵入非常少,在各個(gè)平臺(tái)上的體驗(yàn)也是統(tǒng)一的讹语。
六钙皮、總結(jié)
這幾種方案來(lái)看的話:
- 如果項(xiàng)目中需要復(fù)制的TextView沒(méi)有嵌套在ListView中,那就可以直接大膽地使用
textIsSelectable
屬性顽决,一步搞定短条; - 如果使用到ListView,能改成RecyclerView來(lái)實(shí)現(xiàn)的話才菠,建議直接改成RecyclerView茸时,再加上
textIsSelectable
屬性,也很好解決鸠儿;(方案4) - 如果非要用ListView的話屹蚊,看看是否能在滿足項(xiàng)目需求的情況下,直接使用TextView的點(diǎn)擊事件进每,配合
ClipboardManager
直接自動(dòng)復(fù)制指定內(nèi)容,但記得在復(fù)制完成后給用戶顯示必要的提示命斧。(方案3)========> 本項(xiàng)目就是使用這種方案簡(jiǎn)單粗暴地解決ListView中無(wú)法長(zhǎng)按選擇復(fù)制TextView內(nèi)容的問(wèn)題的田晚。 - 如果這幾種方案還不能解決你的問(wèn)題,可以在其余幾種方案中看看是否有符合要求的国葬。
七贤徒、疑問(wèn)
雖然項(xiàng)目問(wèn)題解決了,但是還有以下幾個(gè)問(wèn)題汇四,在查看了相關(guān)源碼接奈、進(jìn)行一系列測(cè)試后,還是未能解決通孽,暫且記錄下來(lái)序宦,說(shuō)不定以后什么時(shí)候能力達(dá)到了,就能解決了呢背苦?或者哪位小伙伴知道原因互捌,還請(qǐng)不吝賜教~
- 為什么設(shè)置
textIsSelectable
為true
后潘明,ListView的ItemClick事件不響應(yīng)了,明明該有的DOWN秕噪、MOVE钳降、UP事件還是有?ItemClick的觸發(fā)條件是什么腌巾? - 而這時(shí)候遂填,為什么TextView不會(huì)彈出選中框,以及復(fù)制澈蝙、全選等操作按鈕吓坚?而在添加了
android:selectAllOnFocus="true"
后,長(zhǎng)按后顯示紅色選擇框碉克,但不顯示復(fù)制按鈕凌唬,再次點(diǎn)擊紅色選擇框,就會(huì)出現(xiàn)復(fù)制按鈕漏麦。
PS:歡迎關(guān)注SherlockShi博客