自动补全邮箱域名的EditText

前言:终于是狠下心来试着把这个控件做出来了,再努力一点,说不定真有可能实现两端对齐的TextView的。android大法好,加油。

效果图如下,当输入@的时候,就会提示一个邮箱的域名出来,根据输入的不同内容进行不一样的提示,失去焦点的时候,就会把提示的邮箱域名补全到输入框内。

《自动补全邮箱域名的EditText》
《自动补全邮箱域名的EditText》

在实现具体功能之前,我觉得我应该介绍几个drawText时需要注意的,以及常常用到的一些知识点和方法。

getPaint():获取EditText中的Paint对象,使用这个Paint对象能够绘制出和EditText一模一样样式的字体。也可以通过这个Paint对象获取当前输入框内的文本的长度。这个方法非常的重要,我们可以用这个Paint对象实例化自己定义的Paint对象。

《自动补全邮箱域名的EditText》

获取控件的宽和高

getWidth:获取控件在设定好布局后的宽度,在onLayout之前获取这个值都是0

getHeight:获取控件在设定好布局后的高度,在onLayout之前获取这个值都是0

获取EditText中已绘制的文本的长度

measureText (String text)   计算文本的长度,进行了四舍五入。

getTextWidths(String text, float[] widths)  widths数组用来接收每一个字符的宽度,没有进行四舍五入,所以理论上getTextWidths更加的精确,但是我测试的时候输入一样的东西发现他们返回的结果是一样的,还不确定这两个函数的差别是什么,不过一般情况下这两个函数都可以测量文本的长度,一般情况下第一种更加的方便。

getTextBounds(String text, intstart, intend,Rect bounds)  bounds对象用来接收一个Rect对象,这个Rect对象是text所占据的最小的矩形,可以理解成,上下左右紧贴文本的矩形区域,可以通过bounds对象中的width()和Height()方法获取文本的高度和宽度,width()获得的宽度和上面两种方法获取到的结果其实一样的,而高度为某个字符的最高值,效果如下图所示:

《自动补全邮箱域名的EditText》 文本所占据的最小矩形

FontMetric(字体测量):FontMetrics对象可以调用paint对象的getFontMetrics方法获取,这个对象是Paint的静态内部类,里面有5个变量,分别为Top,Ascent,Descent,Bottom,leading,leaddingAPI的注释是多行文本之间的间距,所以这个变量姑且不管它。对于其余的四个值,是相对于基线的值,因为FontMetrics的值跟Paint字体大小有关,而这个值都是相对于BaseLine求得的相对值。Top和Ascent是负值,Bottom和Descent是正值。Ascent和Descent是系统根据TextSize返回的推荐的值,而Top和Bottom则是极值。

不过这些都不是重点,因为当我们调用canvas的drawText函数,只需要关心绘制点就可以了,假设我们不改变TextAlign属性,默认都是从左到右的文本对齐方式,即我们假设绘制点是0到文本的宽度,所以我们只需要关注baseline即可,试想一下,如果我们想要绘制上标和下标,其实就可以尝试设置不同的baseline来排列文字了。

《自动补全邮箱域名的EditText》

注意:默认情况下,EditText是有背景的(background),而这个背景会导致EditText的高度大于Top到Bottom的距离,而EditText的宽度会大于文本的实际宽度。

寻找文本的绘制点

最近不断的绘制EditText的各种线,希望能够从中获取到一些规律,结果发现了一个可能大家都不太重视,但是却是最最基本的,canvas的所有draw方法里面的坐标参数,都是相对于这个控件的左上角,要是我在(0,0)(0,1000)之间绘制一条直线,这个控件的最顶端就会有一条直线,而不是屏幕的最顶端出现一条直线。所以我们绘制的时候,不需要考虑这个控件在什么位置,只需要关心:想要绘制的内容相对于这个控件的位置还有就是这个控件的大小,而(getX(),getY())这个点是控件相对于屏幕的的位置。

我们需要在onDraw方法里面调用canvas的drawText方法,下面来看一下这个drawText方法

drawText(String text, float startX, float startY, Paint paint)

startXPaint设置文本对齐时,默认为左对齐,就是文本占据的最小矩形的左边和x坐标对齐,如果是文本居中的话,那么就是文本的最小矩形的中线和x轴对齐

《自动补全邮箱域名的EditText》 左对齐
《自动补全邮箱域名的EditText》 居中对齐

右对齐就不用演示了,如果设置右对齐,还是用上面图中的绘制点的话,那么将什么会看不到(文本绘制在控件之外)

startY:startY是文本绘制的baseline,这个baseline就是我们上面提到的baseline了。

获取EditText的绘制点

绘制点的X坐标:因为和文本的对齐方式有关,所以我们要根据实际的情况来确定绘制点的X坐标,假如我想让文本居中,比如实现好友列表的字母导航条的时候,就需要把Paint的对齐方式设为居中,x坐标则是getWidth()/2,但是一般情况下我们使用的是左对齐,考虑到EditText可能有background,我们计算X坐标的时候就要小心了,具体的话可以根据getTextBounds获取文本的Left坐标,以及控件getX()获取到控件相对屏幕的x坐标相减,就可以求得绘制点的x坐标了。

绘制点的Y坐标:在TextView中,有一个getBaseLine()的方法,默认这个方法是返回-1的。EditText实现了getBaseLine方法,如果是继承EditText的话,那么直接调用这个函数就可以获得当前EditText的baseline坐标了。如果是继承自TextView或者自定义View,那么说白了,自己来定义baseline的值吧,但是我们从fontMetrics那里得知,top线相对于控件的高度 = baseline+fontMetrics.top。如果我们知道了top线相对控件的高度,也可以反推baseline的值,因为,EditText没有设置背景的话,fontMetrics.top与控件顶部的距离为0。

正文部分:如何实现自动补全邮箱域名的EditTetx

经历了上文一些列的准备工作,我们已经有信心能够在EditText添加附加的文本了。

首先来理清一下需求:

1、一般来说都需要有输入的字数限制的,但是谁知道邮箱名可以定义多长呢,把限制稍微设大一点就可以了吧,字数限制为32,对于输入内容大概就是字母,数字,点号还有@了。

2、如果第一个字符为@号,不弹出文本提示,当且仅当第一个@才会有文本提示,补全@后面的字符,直接取@后面所有的子串进行匹配就好了。

3、获取到附加文本,然后把这个文本附加到输入框的当前文本后面就可以了。

4、当失去焦点的时候,自动补全附加的文本到输入框里面

我打算用两种方法来实现这个需求,第一个方法用的是给文本设置DrawableRight的方式,第二个方法是直接计算文本的长度,从而得到绘制文本的绘制点,然后通过drawText的方式把附加文本显示出来,这两个方法主要是绘制附加文本的方式不一样。之后再比较一下这两种方式的优劣。

第一个方法,

初始化邮箱列表

《自动补全邮箱域名的EditText》

当然了,这列表加的越多,肯定程序跑起来的慢了啊,虽然没个几千上万,估计都是毫秒级就完事了的。

《自动补全邮箱域名的EditText》

重写TextWatcher

一开始初始化时,设置边界,只有当真正修改了输入框内的文本,才调用这一个函数

《自动补全邮箱域名的EditText》

不知道是因为这个匹配的逻辑太简单了,还是最近养成了思考的好习惯,写这个的时候并没有纠结太过,毕竟这里是直接调用java的api来实现的子串匹配,要是自己用字符数组来逐个匹配,估计也是够呛,一开始的时候我是打算这么实现的,只是后来发现,真的太难了T_T

《自动补全邮箱域名的EditText》 只有当附加文本改变的时候才重绘

onDraw

《自动补全邮箱域名的EditText》

onDraw里面最复杂最复杂的其实就是计算文本的绘制点

baseline很容器求,因为是直接在EditText上附加文本,所以直接调用getBaseLine就可以了。

最难的其实是绘制点的x坐标,因为计算这个点,我们需要考虑到文本里面的字符的间隔,而且有一些添加了背景的EditText,还需要考虑padding,margin等,计算到这两个值之后,还需要用EditText中的getPaint()获得的Paint对象来实例化一个自定义的Paint对象,这样就可以获得和EditText一样的字体样式了,改变字体颜色后就可以绘制出和原来输入框内的文本不一样的附加信息了。

失去焦点的时候填充输入框

《自动补全邮箱域名的EditText》

这样的话,才真正实现了自动补全的功能,要是不加这一个的话,那么就仅仅是有一点点提醒功能罢了。总体来说代码量其实并不多,但是需要对控件宽度,FontMetrics,计算文本宽度这些都有个了解才行,因为大多数EditText的背景都是自定义的。

第二种实现方法

第二个方法是在公司的项目里看到的,相对来说稍微复杂了一点,但实际上也是得调用drawText的啊。然后我就想,既然都是drawText,为什么还要定义这么多变量来把它保存起来呢?所以我就自己想了第一种方法来实现了,不过可能还是因为EditText可以设置各种各样的背景吧,使用图片的话就可以使用setCompoundDrawables来添加到文本后面了。当然了,其实也是无法兼顾的啦,不过把东西保存成bitmap,这种思路也是极好的。

直接上代码给大家参考一下吧

《自动补全邮箱域名的EditText》 获得匹配的邮箱的附加文本

《自动补全邮箱域名的EditText》 上部分
《自动补全邮箱域名的EditText》 下部分

最后的最后,洋洋洒洒的写了这么多,不过真心觉得学习了前面FontMetrics等有关drawText的知识后,我对绘制文本,乃至自定义控件都有了更加深刻的认识,虽然自定义控件要是加上手势,加上数据,必然难上百倍千倍,虽然现在写的控件多多少少有一些瑕疵,不过万丈高楼平地起啊,要写出一个功能强大的控件也不是一朝一夕的事情。

    原文作者:黑白咖
    原文地址: https://www.jianshu.com/p/bfea534d6289
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞