本文讲解的是如何自定义一个填空题控件,实现的方式其实有不少,最重要的是了解其中实现的思路和想法,正所谓条条大路通罗马嘛。
在Android系统中,咱们最常使用的用于展现文字和编辑文字的控件,就是TextView和EditView,这两个控件基本上已经可以知足咱们平常大部分开发需求。可是,凡事都有个可是。程序猿基本都会遇到一些比较特殊的需求,而做为一个Android开发者,最多见的特殊需求,就是一个特殊的控件,而这个控件恰好是系统没有提供的。下面就是一个比较特别的控件,一个可填空的控件。要求能够和普通TextView同样展现普通的文字,同时又包含能够编辑的部分,相似EditText。以下:git
看到这个,第一反应就是,这不合理啊,又是展现,又是可编辑,又是换行,没办法实现啊!结果,被人家甩了一句:那啥,学习强国App里面不就有能够填空答题的嘛!我去,这下尴尬了。若是实现不了,岂不是显得本身很Low B!不行,不管如何都得作出来!(才能咽得下这口气!)github
哼,系统没有的控件,我找个第三方的轮子还不行吗?我就不信,世界这么大,还有别人没作好的轮子!因而开启了“常规操做模式”(Google/GitHub/百度,搜索,复制,粘贴)。果不其然,有的是轮子(ヾ(´A`)ノ゚)。好比这两个:Android 使用代码实现一个填空题
Android 基于TextView实现填空题他们有一些共同的特色:面试
1.基于TextView作文字展现
2.基于SpannableString作文字样式变化,文字点击等
3.必需要有一个EditText做为输入
毫无疑问,这是系统提供的,最简单方便的定制一个TextView和EditText结合的方法。可是,他们都存在一些问题,好比canvas
1.非嵌入式的输入,须要在外部提供一个可输入的EditText
2.虽然是嵌入式的输入,可是可编辑文字必需要固定长度,不能根据文字长短动态变化
总而言之,就是体验仍是不够好!无奈之下,萌生了本身造一个轮子的想法。那么,咱们就仿造学习强国,定制一个填空题控件呗。架构
既然决定本身造轮子,必然要先分析一下这个轮子,把这个轮子拆开,看看它包含些什么东西。函数
1.首先,最简单的功能:显示文字
2.其次,实现文字点击,并弹出输入法
3.再次,接收输入法输入
4.最后,光标与文字的输入和删除
在定义View中, 显示文字是一件很是简单的函数调用,无非就是工具
canvas.drawText(text, x, y, paint)
可是,若是你想固然的认为这个是一个简单的事情,那你就大错特错了。学习
首先,对于y坐标,指的是文字的基线(baseLine),而非文字的top坐标,这个坐标能够近似认为是文字的bottom坐标,但并无那么简单。以下图:字体
关于文字的绘制,这篇下面这篇文章讲得很透彻,建议不熟悉的同窗能够看看自定义控件之绘图篇( 五):drawText()详解spa
不可避免的问题,文字过长的时候,咱们须要对它进行换行显示,那么咱们怎么样才能知道何时须要换行呢?这里就涉及到一个文字宽度计算问题在Android中如何计算文字的宽度呢?以下:
private fun measureTextLength(text: String): Float { return mNormalPaint.measureText(text) }
很是简单对不对,measureText这个方法,会根据咱们设定的文字画笔中的字体大小,去测量一段文字的宽度,单位是px。须要注意的是,汉字和数字英文的宽度占位是不同的。 所以在换行的时候,须要特别关注和处理这二者的关系。
既然包含特殊的文字部分,那么咱们须要将其标记出来,以便作特殊的处理。这里,我使用了一个标签<fill style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">来编辑,举个例子:</fill>
这样,通过 String.split(“<fill style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">“)</fill> 后,就能够把这段文字拆分为多个分段。
咱们知道,每一个View均可以接收onTouch事件,而且能够监听到触摸点的x/y坐标。而在绘制文字的过程当中,咱们能够将可编辑文字段的坐标信息记录下来,那么在点击的时候,就能够判断有没有触摸碰撞,若是有,那么就能够弹出输入法。
一般,须要一个可输入文字的控件时,咱们不多本身去定义一个控件,而是直接使用EditText,以致于咱们几乎认为只有EditText能够接收输入法输入。可是,其实Android每一个继承View的控件都是能够接收输入的。那么,如何打开这个功能呢?答案就是如下两个方法:
其中,第一个方法返回true表示,这是一个可编辑控件,能够接收输入法输入。第二个方法,则返回一个InputConnection,用于接收输入。看起来是这样的:
最主要的方法是commitText,输入法输入时,会经过这个方法将文字传输给控件
普通的EditText在输入时,都会有一个光标,用于表示输入或删除的位置。绘制光标,只须要一句代码:
canvas.drawLine(startX, startY, stopX, stopY, paint)
没错,就是绘制一条线,经过修改paint的alpha值(0/255),控制线条的显示和隐藏便可。
关键在于,如何肯定光标的位置。
还记得上面2点,实现可编辑字段的点击吗?当咱们检测到触摸碰撞的时候,咱们就能够根据这个时候触摸点的x坐标,以及文字的长度去判断光标的位置。具体如何实现呢?咱们从最简单的状况来实现。假设,输入的文字都是汉字(前面咱们就说过,汉字和数字英文占位是不同的)。那么,这时,光标所在汉字的索引 = (触摸点x坐标 - 被触摸的编辑字段起始位置的x坐标)/ 单个汉字宽度那么,光标所在实际位置的x坐标就是光标x轴坐标 = (0 至 光标所在汉字的索引)这段文字的长度转化为代码即:
mNormalPaint.measureText(text.substring(0, index))
以下图:
说明:这里的index,指的是文字在可编辑字段中的位置,也就是光标的位置光标起始位置的y坐标,就是被触摸的可编辑字段的y坐标。光标结束位置的x坐标和起始位置相同,y坐标则为其实坐标加上文字高度
当输入的文字包含汉字、英文、数字时,因为英文/数字的占位比汉字小,此时,若是按照汉字的单字来计算光标所在文字的索引,那么此时的索引比实际的索引小。这里就须要一个方法来确认:触摸点x坐标到可编辑字段起始位置x坐标的这段长度,能够存放多少个文字。我采用的方法以下:咱们知道,这段长度,能够放置的最少文字个数,就是汉字的个数。第一步,咱们先取最少的汉字个数,并计算文字长度,若是这时,文字的长度没有超过实际触摸位置。第二步,取下一个文字,并计算文字总长度,判断长度有没有超过实际触摸位置。重复第二步,直到超过实际触摸位置。这时,这是实际的文字索引就是:(取到的最后一个文字的索引 - 1)至此,咱们就获得出实际的光标位置,以及文字索引了。在此基础上,根据光标的位置和文字索引,就能够对文字进行输入和删除了。具体计算以下图所示:
通过上面的分解,基本上,咱们就已经知道实现轮子的各个步骤,剩下的就是将上面的各个步骤拼接起来就好了。固然,具体的代码我就不贴了。你们能够本身去看一下源码,过程并不复杂。自定义控件嘛,每一个人去实现的时候,都会有不同的作法,好比上面计算光标实际位置的方法,确定会有不一样的更好的方法。因此,了解实现的思想和可借助工具方法便可,不必太过较真。最后还一些边边角角的小功能,好比自定义一些可配置属性:文字颜色,字体大小,可编辑字段格式,光标颜色等等;好比根据文字高度,自适应控件高度;好比输入法的弹出和隐藏……再也不细提,具体可看源码。
1.一个复杂的控件每每均可以经过拆解,拆分为一个个简单的功能。2.从最简单的功能开始实现,你会更有信心。3.不要放弃,必定有实现的方法。若是没有,说明你还不够了解一些基础属性,Google之。好了,以上就是给你们介绍的一种定制“填空控件”的思路,固然还有其余的实现方式。仅供你们参考。
源码传送门
https://github.com/ChenLittle...
Android学习PDF+架构视频+面试文档+源码笔记