當前位置:
首頁 > 最新 > Android 自定義文本的「長按選擇複製」,再也不用擔心各種差異了!

Android 自定義文本的「長按選擇複製」,再也不用擔心各種差異了!

承香墨影

只分享最有用的原創技術乾貨!

關注

今天給大家推薦的文章就是說說如何自定義對文本的選擇複製功能。作者也挺有意思,曾經遇到的坑,一年之後,在有能力之後還能記得給它填上。這樣的對技術的探索精神,你能做到嗎?

— 承香墨影

作者 | 寫代碼的猴子

原文 | 《自定義選擇複製功能的實現》

授權 承香墨影 發布


先來個段子:

剛工作時遇到一個特別難搞定的需求,當時沒做出來,感到很羞恥。過了幾年,再一次遇到這個需求,還是沒做出來,只是不再感到羞恥了。

在我剛開始工作的時候,也有過一次這樣的經歷。當時項目中有個需求,讓 TextView 中的文本可以選擇複製,正常來講,應該是很容易實現的,直接按照下面的設置就可以了:

但是,這個簡單的實現並不是完美的,主要有幾個問題:

1、不同版本選擇複製樣式不統一:在原生系統上 6.0 之前和之後的操作樣式是不同的,這裡不得不說,6.0 以下的這個選擇複製操作交互很不合理,且對應用的界面侵入太多。

2、萬惡的國產 ROM 問題:當時公司測試同事提 bug 反饋,在 vivo 手機上這麼設置,長按之後並沒有效果。(再一次吐槽亂改系統的國產 ROM,這也是為什麼 Android 開發比起 iOS 費事費力的原因之一)

3、可定製性不高:如果僅僅是一個選擇複製的功能,不考慮以上兩個問題,還能湊合搞定,但是假如多個需求,選中文字之後直接進行某個操作,比如收藏、發送給好友,此時原生的選擇複製功能可能就不足以勝任了。

以上說了這麼多,問題的解決辦法就是:自己寫一個選擇複製的功能,這樣以上三個問題都能很好地解決了。

看起來很容易,但是對於當時剛剛入門的我來說,這是個完全沒頭緒的任務。

時隔一年之後,再遇到這個需求,這次通過 Google、GitHub ,以及參考 SDK 23 中 TextView 源碼,基本上實現了自定義選擇複製的功能,效果如下:

保證所有的平台上顯示效果一致,彈出的操作菜單可以自己定製,並設置相應的操作。


在開始具體的實現之前,先確定下實現的要求:

儘可能保證和 Android 6.0 原生選擇複製一樣的交互和基礎功能

儘可能不需要侵入太多,為了實現選擇複製功能,重新自定義 TextView 的方式是不夠優雅的,特別是考慮到項目中本來就已經使用了自定義的 TextView ,一旦需求變更,改動成本很大

可用的自定義配置

本文最終實現的使用方式如下所示,均滿足以上的實現要求:

整個自定義的選擇複製功能視圖上主要有三個部分:

選擇游標

選中的文本

操作框

在具體實現中有以下要點:

自定義選擇游標,可以拖動定位選中文本

文本的選中狀態

操作框的顯示,以及對應操作的處理

在可滑動布局中的特殊處理,例如在 ScrollView 中,當視圖滾動時隱藏或者移動選擇游標,隱藏操作框,停止滑動時重新顯示選擇游標和操作框

選中文本後,點擊 TextView 取消選擇


在開始實踐之前,查找資料是少不了的,首先找到了 《記劃詞模塊重構感受|開源實驗室-張濤》 這篇文章,但是這篇文章中更多是提供了一個改進某個開源項目的思路,並沒有給出具體的代碼,而且連那個開源項目也沒給出地址。

後來通過搜索關鍵字,找到了那個開源項目:

https://github.com/zhouray/SelectableTextView

如張濤吐槽的那樣,這個項目的實現確實不夠優雅,主要存在兩個問題:

自定義 TextView 實現的,侵入太多。

解決嵌套在滑動布局中的處理太簡單粗暴,竟然自定義了一個 ScrollView 來處理,應用到實際場景中是存在問題的。

如果你有時間可以看一下這個項目的代碼,在本文後面的實現中,也部分參考了該項目。

參考上面提到的文章和開源項目,實現思路基本確定了:

選擇游標使用 PopupWindow 實現,並重寫 Touch 事件處理邏輯,實現拖動定位選擇文本。

選中文本使用 來顯示,比較簡單。

操作框同樣使用 PopupWindow 實現,重點是處理好顯示的位置。

大致的思路確定,接下來就是具體的實現了。


自定義的選擇複製類取名為 ,其有一個欄位 ,持有需要設置選擇複製的 對象。


由於 的文本的 類型是 時才可以設置 Span ,實現選中的效果,因此在一開始先給 TextView 設置下:

接下來給 TextView 設置相關的點擊、長按、Touch 事件:

其中 記錄了觸摸點坐標,用於後面的選擇文本的位置定位以及選擇游標的顯示,即傳遞給 方法。

中的處理比較簡單,重置選中文本信息、隱藏選擇相關的 View 。

直接看一下 和 的實現:


在 show 方法開始,因為之前可能已經顯示了選擇相關的 View ,比如先長按 TextView 的 A 點,然後彈出選擇游標、操作框,此時再長按 B 點,此時再次彈出選擇游標和操作框時,就需要先隱藏之前的相關 View 了,這裡就這樣簡單粗暴地處理了下。

是一個很有意思的地方,這裡參考了前面提到的開源項目裡面的實現,這個方法通過傳入 TextView 中一個點的坐標,就可以計算出來對應的最接近的那個文字的索引,簡單說明如下:

通過傳入『種』那個字附近的某個點的坐標 (x,y),就可以得出『種』在 TextView 的文本中的索引是 9 (從 0 開始計數)。

方法如下:

這裡涉及到 TextView 的文本布局類 Layout ,雖然看過這塊的部分源碼,但是這裡的處理還是有點懵,本文就不多深入了,有興趣的話可以自行了解下這塊的源碼。

文本的選中顯示是在 方法中處理的,重點是設置 Span 和記錄選中的文本信息:

其中處理了下可能存在的 endPos 小於 startPos 的情況,進行了一次交換,後面就是設置 已經記錄下選中文本的信息,已經設置了選中監聽時的回調。

其中 mSelectionInfo 是 類的一個簡單實例,該類就三個欄位,選中文字的開始位置、結束位置和選中的文本:

方法顧名思義就是顯示選擇游標,因為是 PopupWindow 實現的,重點就是顯示位置的確定,這裡再次涉及到 Layout 相關的 API :

這裡和之前的是反的,通過文本中的文字索引,來獲取到對應的點的坐標。然後顯示 PopupWindow 即可。

最後是顯示操作框,同樣是一個 PopupWindow ,這裡的細節後面再展開。


這裡沒啥好說的,就是判空下左右選擇游標和操作框,如果非空,則調用對應的 方法

這裡基本的流程和相關的實現細節已大概講述了下,接下來就是就是選擇游標和操作框的實現。


由於游標的移動涉及到文字的選中,以及操作框的顯隱、定位,就直接實現為 的內部類。直接上代碼:

直接繼承 PopupWindow 的話,沒有 onDraw 方法 ,這裡直接繼承 View ,然後在 CursorHandle 的構造函數中初始化了一個 PopupWindow ,並將 CursorHandle 實例作為 contentView 傳遞進去,然後在 方法中繪製了自定義的選擇游標,仿照 6.0 的選擇游標效果。

這個也是繪製起來也是很簡單的,一個正方形和一個圓組合下即可,處理下是左邊還是右邊就可以了,具體參照上面的代碼。

接下來就是設置相關的觸摸事件,響應拖動游標時更新選中的文本。

在游標移動時,隱藏操作框,停止移動時,再顯示操作框。

在觸摸發生移動時,即 時,更新游標位置和選中的文本, 方法如下:

在一開始的實現中, 方法沒這麼複雜,但是考慮到左邊的游標在移動到右邊游標的右邊時,如下面的動圖所示:

GIF

img

此時就需要多一點處理,左邊的右邊變右邊,右邊的游標變左邊,同時選中的文本也需要重新變換起點位置,原來是 end ,現在則變成了 start 。

具體的邏輯實現就是根據之前選中的文本的前後位置信息,進行前後位置的交換。同時調整游標的方向,更新視圖,這個邏輯在 方法中:

更新選擇游標位置:由於游標的位置處理成只和選中的文本有關,因而處理起來較為簡單,在上面的反轉變化中,只要選中的文本正確變化了,那麼這裡的游標位置更新就是正確的。


操作框的實現則簡單的多,就是自定義布局的 PopupWindow ,然後處理下內部的 View 的點擊事件即可,直接貼代碼:

在顯示的之後,判斷了下是否會顯示到屏幕外面,如果會超出屏幕,則做一下微調即可。


在一開始的實現要點中就提到,需要注意一下嵌套在滾動視圖中的處理,在嘗試了一些方法之後,最終直接設置 來解決,具體代碼如下:

這倒是解決了滑動時可以隱藏相關的選擇控制項的問題,但是停止滾動之後呢,如何重新顯示選擇控制項呢?

在經過一些嘗試之後,發現了 這個介面,在 TextView 發生滾動時期間一直在被調用,因此在這個介面里處理重新顯示選擇控制項的邏輯是合適的:

在這樣的設置之後,確實能保證停止滾動時重新顯示選擇相關的控制項,但是整個滾動過程變得異常卡頓。

原因其實很簡單,前面也提到了, 方法在 TextView 發生滾動時期間一直在被調用,然後這裡一直處理顯示選擇控制項的邏輯,能不卡頓么?

最後的解決方法是在源碼中找到的,將 方法替換成 方法,

很巧妙的方法,通過延遲調用具體的邏輯,避免了一直調用顯示選擇控制項的邏輯,又學習到了。


在一開始沒處理這個的時候,一直報如下的錯誤:

這麼明顯的錯誤可不能不管,處理起來也很簡單:

將上面添加 Listener 也移除,同時隱藏響應的視圖並置空。


至此,自定義的選擇複製功能完成,效果如下,

GitHub 地址:

https://github.com/laobie/SelectableTextHelper

GIF

img

在開發之初,通過簡單的查閱資料,梳理了個大概的實現思路,並考慮到實現中需要注意到的點,保證在開發中保持足夠的警惕,不給自己挖坑。在整個開發過程中,通過閱讀他人的源碼,以及直接看官方的源碼,一點點解決所遇到的問題,以及一點點地嘗試,都是一次不錯的開發經歷,也算是彌補了當初沒做出來這個任務的缺憾。

當然,這個項目還是有很多值得優化的地方,比如一些邊界狀態的處理,多個 TextView 的選擇複製的場景等等,代碼上的內部類的使用也是不夠優雅的,不能夠做到足夠的解耦,都是有優化空間的,歡迎溝通交流。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 承香墨影 的精彩文章:

開發過 Android TV App 嗎?推薦個好用的工具給你!

TAG:承香墨影 |