前言:終于是狠下心來試著把這個控件做出來了,再努力一點,說不定真有可能實現(xiàn)兩端對齊的TextView的。android大法好,加油。
效果圖如下,當輸入@的時候,就會提示一個郵箱的域名出來,根據(jù)輸入的不同內(nèi)容進行不一樣的提示,失去焦點的時候,就會把提示的郵箱域名補全到輸入框內(nèi)。
在實現(xiàn)具體功能之前,我覺得我應該介紹幾個drawText時需要注意的,以及常常用到的一些知識點和方法。
getPaint():獲取EditText中的Paint對象,使用這個Paint對象能夠繪制出和EditText一模一樣樣式的字體。也可以通過這個Paint對象獲取當前輸入框內(nèi)的文本的長度。這個方法非常的重要,我們可以用這個Paint對象實例化自己定義的Paint對象。
獲取控件的寬和高
getWidth:獲取控件在設(shè)定好布局后的寬度,在onLayout之前獲取這個值都是0
getHeight:獲取控件在設(shè)定好布局后的高度,在onLayout之前獲取這個值都是0
獲取EditText中已繪制的文本的長度
measureText (String text) ? 計算文本的長度,進行了四舍五入。
getTextWidths(String text, float[] widths) ?widths數(shù)組用來接收每一個字符的寬度,沒有進行四舍五入,所以理論上getTextWidths更加的精確,但是我測試的時候輸入一樣的東西發(fā)現(xiàn)他們返回的結(jié)果是一樣的,還不確定這兩個函數(shù)的差別是什么,不過一般情況下這兩個函數(shù)都可以測量文本的長度,一般情況下第一種更加的方便。
getTextBounds(String text, intstart, intend,Rect bounds) ?bounds對象用來接收一個Rect對象,這個Rect對象是text所占據(jù)的最小的矩形,可以理解成,上下左右緊貼文本的矩形區(qū)域,可以通過bounds對象中的width()和Height()方法獲取文本的高度和寬度,width()獲得的寬度和上面兩種方法獲取到的結(jié)果其實一樣的,而高度為某個字符的最高值,效果如下圖所示:
FontMetric(字體測量):FontMetrics對象可以調(diào)用paint對象的getFontMetrics方法獲取,這個對象是Paint的靜態(tài)內(nèi)部類,里面有5個變量,分別為Top,Ascent,Descent,Bottom,leading,leaddingAPI的注釋是多行文本之間的間距,所以這個變量姑且不管它。對于其余的四個值,是相對于基線的值,因為FontMetrics的值跟Paint字體大小有關(guān),而這個值都是相對于BaseLine求得的相對值。Top和Ascent是負值,Bottom和Descent是正值。Ascent和Descent是系統(tǒng)根據(jù)TextSize返回的推薦的值,而Top和Bottom則是極值。
不過這些都不是重點,因為當我們調(diào)用canvas的drawText函數(shù),只需要關(guān)心繪制點就可以了,假設(shè)我們不改變TextAlign屬性,默認都是從左到右的文本對齊方式,即我們假設(shè)繪制點是0到文本的寬度,所以我們只需要關(guān)注baseline即可,試想一下,如果我們想要繪制上標和下標,其實就可以嘗試設(shè)置不同的baseline來排列文字了。
注意:默認情況下,EditText是有背景的(background),而這個背景會導致EditText的高度大于Top到Bottom的距離,而EditText的寬度會大于文本的實際寬度。
尋找文本的繪制點
最近不斷的繪制EditText的各種線,希望能夠從中獲取到一些規(guī)律,結(jié)果發(fā)現(xiàn)了一個可能大家都不太重視,但是卻是最最基本的,canvas的所有draw方法里面的坐標參數(shù),都是相對于這個控件的左上角,要是我在(0,0)(0,1000)之間繪制一條直線,這個控件的最頂端就會有一條直線,而不是屏幕的最頂端出現(xiàn)一條直線。所以我們繪制的時候,不需要考慮這個控件在什么位置,只需要關(guān)心:想要繪制的內(nèi)容相對于這個控件的位置還有就是這個控件的大小,而(getX(),getY())這個點是控件相對于屏幕的的位置。
我們需要在onDraw方法里面調(diào)用canvas的drawText方法,下面來看一下這個drawText方法
drawText(String text, float startX, float startY, Paint paint)
startX:Paint設(shè)置文本對齊時,默認為左對齊,就是文本占據(jù)的最小矩形的左邊和x坐標對齊,如果是文本居中的話,那么就是文本的最小矩形的中線和x軸對齊
右對齊就不用演示了,如果設(shè)置右對齊,還是用上面圖中的繪制點的話,那么將什么會看不到(文本繪制在控件之外)
startY:startY是文本繪制的baseline,這個baseline就是我們上面提到的baseline了。
獲取EditText的繪制點
繪制點的X坐標:因為和文本的對齊方式有關(guān),所以我們要根據(jù)實際的情況來確定繪制點的X坐標,假如我想讓文本居中,比如實現(xiàn)好友列表的字母導航條的時候,就需要把Paint的對齊方式設(shè)為居中,x坐標則是getWidth()/2,但是一般情況下我們使用的是左對齊,考慮到EditText可能有background,我們計算X坐標的時候就要小心了,具體的話可以根據(jù)getTextBounds獲取文本的Left坐標,以及控件getX()獲取到控件相對屏幕的x坐標相減,就可以求得繪制點的x坐標了。
繪制點的Y坐標:在TextView中,有一個getBaseLine()的方法,默認這個方法是返回-1的。EditText實現(xiàn)了getBaseLine方法,如果是繼承EditText的話,那么直接調(diào)用這個函數(shù)就可以獲得當前EditText的baseline坐標了。如果是繼承自TextView或者自定義View,那么說白了,自己來定義baseline的值吧,但是我們從fontMetrics那里得知,top線相對于控件的高度 = baseline+fontMetrics.top。如果我們知道了top線相對控件的高度,也可以反推baseline的值,因為,EditText沒有設(shè)置背景的話,fontMetrics.top與控件頂部的距離為0。
正文部分:如何實現(xiàn)自動補全郵箱域名的EditTetx
經(jīng)歷了上文一些列的準備工作,我們已經(jīng)有信心能夠在EditText添加附加的文本了。
首先來理清一下需求:
1、一般來說都需要有輸入的字數(shù)限制的,但是誰知道郵箱名可以定義多長呢,把限制稍微設(shè)大一點就可以了吧,字數(shù)限制為32,對于輸入內(nèi)容大概就是字母,數(shù)字,點號還有@了。
2、如果第一個字符為@號,不彈出文本提示,當且僅當?shù)谝粋€@才會有文本提示,補全@后面的字符,直接取@后面所有的子串進行匹配就好了。
3、獲取到附加文本,然后把這個文本附加到輸入框的當前文本后面就可以了。
4、當失去焦點的時候,自動補全附加的文本到輸入框里面
我打算用兩種方法來實現(xiàn)這個需求,第一個方法用的是給文本設(shè)置DrawableRight的方式,第二個方法是直接計算文本的長度,從而得到繪制文本的繪制點,然后通過drawText的方式把附加文本顯示出來,這兩個方法主要是繪制附加文本的方式不一樣。之后再比較一下這兩種方式的優(yōu)劣。
第一個方法,
初始化郵箱列表
當然了,這列表加的越多,肯定程序跑起來的慢了啊,雖然沒個幾千上萬,估計都是毫秒級就完事了的。
重寫TextWatcher
一開始初始化時,設(shè)置邊界,只有當真正修改了輸入框內(nèi)的文本,才調(diào)用這一個函數(shù)
不知道是因為這個匹配的邏輯太簡單了,還是最近養(yǎng)成了思考的好習慣,寫這個的時候并沒有糾結(jié)太過,畢竟這里是直接調(diào)用java的api來實現(xiàn)的子串匹配,要是自己用字符數(shù)組來逐個匹配,估計也是夠嗆,一開始的時候我是打算這么實現(xiàn)的,只是后來發(fā)現(xiàn),真的太難了T_T
onDraw
onDraw里面最復雜最復雜的其實就是計算文本的繪制點
baseline很容器求,因為是直接在EditText上附加文本,所以直接調(diào)用getBaseLine就可以了。
最難的其實是繪制點的x坐標,因為計算這個點,我們需要考慮到文本里面的字符的間隔,而且有一些添加了背景的EditText,還需要考慮padding,margin等,計算到這兩個值之后,還需要用EditText中的getPaint()獲得的Paint對象來實例化一個自定義的Paint對象,這樣就可以獲得和EditText一樣的字體樣式了,改變字體顏色后就可以繪制出和原來輸入框內(nèi)的文本不一樣的附加信息了。
失去焦點的時候填充輸入框
這樣的話,才真正實現(xiàn)了自動補全的功能,要是不加這一個的話,那么就僅僅是有一點點提醒功能罷了。總體來說代碼量其實并不多,但是需要對控件寬度,F(xiàn)ontMetrics,計算文本寬度這些都有個了解才行,因為大多數(shù)EditText的背景都是自定義的。
第二種實現(xiàn)方法
第二個方法是在公司的項目里看到的,相對來說稍微復雜了一點,但實際上也是得調(diào)用drawText的啊。然后我就想,既然都是drawText,為什么還要定義這么多變量來把它保存起來呢?所以我就自己想了第一種方法來實現(xiàn)了,不過可能還是因為EditText可以設(shè)置各種各樣的背景吧,使用圖片的話就可以使用setCompoundDrawables來添加到文本后面了。當然了,其實也是無法兼顧的啦,不過把東西保存成bitmap,這種思路也是極好的。
直接上代碼給大家參考一下吧
最后的最后,洋洋灑灑的寫了這么多,不過真心覺得學習了前面FontMetrics等有關(guān)drawText的知識后,我對繪制文本,乃至自定義控件都有了更加深刻的認識,雖然自定義控件要是加上手勢,加上數(shù)據(jù),必然難上百倍千倍,雖然現(xiàn)在寫的控件多多少少有一些瑕疵,不過萬丈高樓平地起啊,要寫出一個功能強大的控件也不是一朝一夕的事情。