Android復(fù)制TextView內(nèi)容常用方法匯總

一、前言

最近在項(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 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屬性就可以觸發(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è)置textIsSelectabletrue后潘明,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博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末客税,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撕贞,更是在濱河造成了極大的恐慌更耻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捏膨,死亡現(xiàn)場(chǎng)離奇詭異秧均,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)号涯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門目胡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人链快,你說(shuō)我怎么就攤上這事誉己。” “怎么了域蜗?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵巨双,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我霉祸,道長(zhǎng)筑累,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任丝蹭,我火速辦了婚禮慢宗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己婆廊,他們只是感情好迅细,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淘邻,像睡著了一般茵典。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宾舅,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天统阿,我揣著相機(jī)與錄音,去河邊找鬼筹我。 笑死扶平,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔬蕊。 我是一名探鬼主播结澄,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岸夯!你這毒婦竟也來(lái)了麻献?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猜扮,失蹤者是張志新(化名)和其女友劉穎勉吻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旅赢,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡齿桃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煮盼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片短纵。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖僵控,靈堂內(nèi)的尸體忽然破棺而出踩娘,到底是詐尸還是另有隱情,我是刑警寧澤喉祭,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站雷绢,受9級(jí)特大地震影響泛烙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翘紊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一蔽氨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦鹉究、人聲如沸宇立。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)妈嘹。三九已至,卻和暖如春绍妨,著一層夾襖步出監(jiān)牢的瞬間润脸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工他去, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毙驯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓灾测,卻偏偏與公主長(zhǎng)得像爆价,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子媳搪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,708評(píng)論 22 664
  • 最近做了一個(gè)Android UI相關(guān)開源項(xiàng)目庫(kù)匯總铭段,里面集合了OpenDigg 上的優(yōu)質(zhì)的Android開源項(xiàng)目庫(kù)...
    OpenDigg閱讀 17,165評(píng)論 6 223
  • 早上和以前的一個(gè)朋友約會(huì)。我們約著去公園運(yùn)動(dòng)蛾号,5點(diǎn)多一點(diǎn)她來(lái)個(gè)信息稠项,我還在早課中,等我念完一部經(jīng)鲜结,我回復(fù)“10分鐘...
    清文雅書閱讀 218評(píng)論 0 0
  • 如題展运,我們使用recyclerview的時(shí)候,如果沒(méi)有設(shè)置顯示條目的margin精刷,或者padding的話拗胜,是沒(méi)有分...
    黑鍵手記閱讀 1,699評(píng)論 1 2