声明:博文未经授权一律不允转载
用Kotlin封装一个小巧玲珑的登录输入控件
- 图示
| 左滑清空 | 明/密文码切换 | 自定义icon | tips提示 |
| — | — | — | — |
| | | | | - TODOList Gists
- 使用GithubAction+Gradle插件将包发布到jcenter;
- 封装清空触发条件;
- 封装点击明/密文切换触发条件;
- 控制tips提示的时机;
- 控件属性参数化(TypedArray);
- 更多细节
- 版本适配,AppCompat;
- 禁用setError功能;
- 发布到jcenter注意事项;
TODOList Gists正文
“使用GithubAction+Gradle插件将包发布到jcenter”
Github Actions 中文链接 英文链接 相信关注Github的都不陌生。从去年(2019)11月全面发布至今,已有大量攻城狮在上面定义并且分享自己的Actions,这里默认大家都了解过就一笔带过了。已了解的朋友可以直接跳过。
此项目当然也拥有自己的Workflow,从构建,收集产物,发布Release,到最后publish到jcenter,都只需要通过定义一个自动流水线(Workflow(Workflow包含多个Actions))即可完成所有工序。
以下是对本项目Workflow的yml配置文件的介绍,此Workflow的定义可以简单概括如下:- 设定触发条件;
- master分支有push的时候触发;
- 有相应格式的tag的时候触发;
- 指定执行环境和定义流水线步骤;
- checkout代码;
- 设置执行的Java环境;
- 执行打包构建得到构建产物;
- 收集构建产物;
- 上传发布到jcenter;1234567891011121314151617181920212223242526272829303132333435363738name: Android CI # 定义workflow名称# 1. 设定触发条件;on: # workflow触发条件,以下设置多个触发条件,也可以使用cron语法制定触发的时间push:branches: [ master ] # 在master分支上发生push行为的时候开始执行tags: # 满足以下格式的tag发布到远程仓库中的时候开始执行- 'v*.*'- 'v*.*.rc*'- 'v*.*.dev*'# 2. 指定执行环境 和 定义流水线步骤;jobs: # 定义workflow所有相关配置build:# 指定执行环境;runs-on: ubuntu-latest # 定义运行环境# 定义流水线步骤;steps: # 定义workflow所有需要执行的步骤# 步骤1:checkout代码- uses: actions/checkout@v2# 步骤2:设置执行的Java环境- name: set up JDK 1.8uses: actions/setup-java@v1with:java-version: 1.8# 步骤3:执行打包构建得到构建产物- name: Buildrun: ./gradlew :lib-simpleloginet:assembleRelease# 步骤4:收集构建产物- name: Releaseuses: softprops/action-gh-release@v1if: startsWith(github.ref, 'refs/tags/')with:files: |./lib-simpleloginet/build/outputs/aar/lib-simpleloginet-release.aarenv:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}# 步骤5:上传发布到jcenter- name: BintrayUploadrun: ./gradlew :lib-simpleloginet:bintrayUpload -PapiKey=${{ secrets.BINTRAY_API_KEY }}
- 设定触发条件;
“封装清空触发条件”
此功能整体框架和伪代码如下:- 获取VelocityTracker实例和定义阈值;
- 收集每一个MotionEvent事件;
- 记录事件序列初始和结尾的x坐标;
- 用2和3收集到的条件区分点击和滑动;
- 用2和3收集到的条件区分计算滑动速度是否满足触发清空的条件;
- 清空12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152//监听滑动速度,封装清空触发条件伪代码//1. 获取VelocityTracker实例和定义阈值;//获取android.view.VelocityTracker实例对象,用于监听滑动速度val velocityTracker: VelocityTracker = VelocityTracker.obtain()//定义滑动的速度阈值,用于识别确实是滑动,因为手指头点击的时候也可能带有些许滑动//这里直接使用android.view.ViewConfiguration#getScaledMinimumFlingVelocity提供给我们的阈值来定义val clearTouchVelocityMinSlop = ViewConfiguration.get(context).scaledMinimumFlingVelocity//定义清空滑动距离的阈值//这里直接使用android.view.ViewConfiguration#scaledTouchSlop的两倍来定义val clearTouchSlop = ViewConfiguration.get(context).scaledTouchSlop * 2//省略一些代码override fun onTouch(v: View?, event: MotionEvent?): Boolean {return event?.let {//2. 收集每一个MotionEvent//每一个MotionEvent都告知velocityTracker//获取屏幕滑动速度的方法就是收集一连串的MotionEvent事件,其中的三个关键数据x、y坐标和事件发生的时间戳是得知滑动速度的关键因素velocityTracker.addMovement(it)//省略一些代码when (it.action) {MotionEvent.ACTION_DOWN -> {//3. 记录事件序列初始x坐标;downEventX = it.x}//在UP和CANCEL事件的时候进行速度的计算MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {//3. 记录事件序列结尾x坐标;it.xif (isClearMoveWhenUp(it.x, downEventX)) {//6. 清空setText("")}}}true}?: false}//省略一些代码//关键方法:根据速度和滑动距离判断是否触发清空操作private fun isClearMoveWhenUp(v0: Float, v1: Float): Boolean {//5. 用2和3收集到的条件区分计算滑动速度是否满足触发清空的条件;//判断x方向的速度是否超过了阈值clearTouchVelocityMinSlopfun isClearMoveWhenUp0(): Boolean {velocityTracker.computeCurrentVelocity(100, clearTouchVelocityMaxSlop.toFloat())val result = abs(velocityTracker.xVelocity) >= clearTouchVelocityMinSlopvelocityTracker.clear()return result}//4. 用2和3收集到的条件区分点击或滑动;//判断滑动距离是否超过了阈值clearTouchSlopfun isClearMoveWhenUp1(v0: Float, v1: Float): Boolean = clearTouchSlop <= abs(v0 - v1)return isClearMoveWhenUp0() && isClearMoveWhenUp1(v0, v1)}
“封装点击明/密文切换触发条件”
此功能整体框架和伪代码如下:- 定义点击的时间阈值;
- 限定能够点击的范围为右边的icon所占的位置,记录DOWN事件的时间戳和X、Y坐标;
- 比较DOWN/UP事件的时间戳差值判断是否是一个点击;
- 比较DOWN/UP事件的X、Y坐标差值判断是否是一个点击(限定能够点击的范围);
- 修改明/密文状态icon的显示;
- 修改明/密文的显示;1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859//封装点击明/密文切换触发条件伪代码//1. 定义点击的时间最小阈值,超过阈值不触发点击;val tapTimeout = ViewConfiguration.getTapTimeout() * 2//记录触摸事件数据的实体类private data class TouchTripleVo(val touchEventTime: Long, val x: Float, val y: Float)//省略一些代码override fun onTouch(v: View?, event: MotionEvent?): Boolean {return event?.let {//省略一些代码when (it.action) {MotionEvent.ACTION_DOWN -> {val downEvent = it//只有密码框的[2]才会有值 0左1上2右3下compoundDrawables[2]?.apply {val width = this.bounds.width()//2. 限定能够点击的范围为右边的icon做占得位置;if (downEvent.x > measuredWidth - width) {//2. 记录DOWN事件的时间戳和X、Y坐标;//将Down事件的时间x、y坐标记录在一个实体类中touchEventTriple = TouchTripleVo(downEvent.eventTime, downEvent.x, downEvent.y)}}}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {val upEvent = it//3. 比较DOWN/UP事件的时间戳差值判断是否是一个点击;val isClickTap0: Boolean = tapTimeout > upEvent.eventTime.minus(touchEventTriple?.touchEventTime ?: 0L)//4. 比较DOWN/UP事件的X、Y坐标差值判断是否是一个点击;val isClickTap1: Boolean = touchSlop >= abs(upEvent.x - touchEventTriple?.x ?: 0f)val isClickTap2: Boolean = touchSlop >= abs(upEvent.y - touchEventTriple?.y ?: 0f)if(isClickTap0 && isClickTap1 && isClickTap2) {//5. 修改明/密文状态icon的显示;//6. 修改明/密文的显示;updateAndGetTextVisible()//省略:通过接口回调通知别人自己的状态是明文还是密文}}}true}?: false}private fun updateAndGetTextVisible(): Boolean {if (布尔值表达式or方法:当前是否密文) {修改图标为明文状态} else {修改图标为密文状态}//修改密码框的明/密文字显示return (布尔值表达式or方法:修改后当前是否密文).apply {if (this == true) {inputType =InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD} else {inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD}}}
“控制tips提示的时机”
虽然提供了滑动清空的功能,但是如果没有明显的提示用户也是不知道的,这里我们参考原生的提示功能自己封装一个PopupWindow提示,原生的提示功能是通过调用android.widget.TextView#setError触发的(EditText是TextView的子类),感兴趣的朋友可以去了解一哈,也是对PopupWindow的一个封装。
代码比较简单,不多加注释了,主要的做两件事情:1. 展示tips提示; 2. 在合适时机触发1;
12345678910111213141516171819202122232425262728293031323334353637//1. 展示tips提示;private fun showClearTips() {val textView = 代码生成一个textviewval popupWindow = PopupWindow(textView)//略:popupWindow参数配置val radius: Float = 获取圆角值val bgDrawable = ShapeDrawable(RoundRectShape(FloatArray(8) { radius }, null, null))//略:设置bgDrawable颜色if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {textView.background = bgDrawable} else {textView.setBackgroundDrawable(bgDrawable)}//设置不可点击popupWindow.isTouchable = falsepostDelayed({if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {popupWindow.showAsDropDown(this, 0, 10, Gravity.BOTTOM)} else {popupWindow.showAsDropDown(this, 0, 10)}val dismissRunnable = Runnable { popupWindow.dismiss() }popupWindow.setOnDismissListener { removeCallbacks(dismissRunnable) }//3500秒后自动消失postDelayed(dismissRunnable, 3000)}, 500)//500毫秒给键盘弹出的时间}//2. 在合适时机触发1;override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {super.onFocusChanged(focused, direction, previouslyFocusedRect)if (focused && !isLastLaunchExist) {isLastLaunchExist = true//显示popupwindow 提示滑动清空showClearTips()}}“控件属性参数化(TypedArray)”
定义attrs.xml文件,在构造函数中解析这些属性变成相应的参数即可,解析过程都是一些模板代码这里就不再赘述了,仅贴出attrs.xml文件源码如下:1234567891011121314<resources><declare-styleable name="SimpleLoginEt"><!--清空提示的文字,注意需要使用R.string.xxx引用--><attr name="clear_tips" format="reference" /><!--清空提示框的背景色--><attr name="clear_tips_bg_color" format="color" /><!--清空提示框的圆角值--><attr name="clear_tips_bg_radius" format="dimension" /><!--密码模式下的右边icon的明文状态,注意需要使用R.drawable.xxx引用--><attr name="icon_end_open" format="reference" /><!--密码模式下的右边icon的密文状态,注意需要使用R.drawable.xxx引用--><attr name="icon_end_close" format="reference" /></declare-styleable></resources>“其他细节”
- 为了方便不同功能模块的讲解,文中设计代码的部分均采用伪代码的形式,实际上的代码和文中代码会有出入,感兴趣的朋友可以查看项目的[源码]。(https://github.com/halohoop/SimpleLoginEditText)。
- “版本适配,AppCompat”
EditText如果有Android版本适配要求需要使用AppCompatEditText作为基类的话,我们拷贝一份SimpleLoginEt文件,然后将
class SimpleLoginEt : EditText
改为
class AppCompatSimpleLoginEt : AppCompatEditText
即可。 “禁用setError功能”
由于此功能的触发会导致drawableRight的icon被修改,因此我们需要禁用此功能;1234override fun setError(error: CharSequence?, icon: Drawable?) {//禁用此功能,因为会影响设置drawable,感兴趣的同学也可以自定义自己的错误提示//super.setError(error, icon)}“发布到jcenter注意事项”
首次上传发布到jcenter不能够立即在jcenter的maven库中找到,还需要手动[Add to JCenter]
申请发布一次,如下图:
图5:
图6:
申请发布首次之后,等一小段时间后就能够在你的bintray账户的收件箱中收到通过的回复,如下图:
之后再提交新的发布版本就会自动发布到jcenter的maven库中。
不了解如何发布aar包到jcenter的朋友,可以参考本文结尾中相关阅读的文章。
- 请一起来完善,欢迎给我提PR
个人水平有限,欢迎读者勘误。
项目基于开源协议[Apache-2.0]托管在Github上,地址为https://github.com/halohoop/SimpleLoginEditText。- 方向1:我们能看到AppCompatSimpleLoginEt和SimpleLoginEt这两个类的代码90%以上都是重复的,为了后期维护方便,不必维护两套代码,我们可以抽取出来,欢迎给我PR,一起来完善这个项目;
- 方向2:按照目前的实现左右的滑动都可以触发清空,可以区分一下将其中一个方向的滑动去实现其他功能;
- 方向3:国际化,翻译,RTL适配与完善;
- 方向4:完善单元测试;
- More improvements are welcomed;
- References