自定义验证码数字键盘

自定义验证码数字键盘

序言

我们会遇到很多输入六位数,四为数的验证码界面
@[toc]

最终效果如下

在这里插入图片描述

问题

那我们做这类界面的时候遇到的什么问题呢

  1. 需要有四个或者六个固定的输入框供用户输入
  2. 输入框内不能有光标,用户只能从后往前删除数字
  3. 如果用edittext那么会一直有光标可以选择,文字输入一个之后跳转到另一个会有一定的bug,包括edittext的聚焦,光标显示在不对的输入框内的问题等等
  4. 如果用原生的键盘,那么有的时候不可避免的用户可以切换到标点符号输入键盘,有的甚至可以到英文键盘,这样需要过滤掉这些不能输入的字符。而且用户切换过去之后不好切回来体验极差
  5. 输入框的唤醒,我们很难很好的去操作输入框的显示和隐藏,这是很蛋疼的代码,可能你要写很多代码才能去隐藏掉原生的输入框,然后在有些机型上可能这个代码并没什么效果。

预期操作方式

那我们预期达到的操作方式是什么呢,我们假设用户是傻子或者好奇心极强,以及故意找漏洞的不怀好意的人。

  1. 我们希望用户只能点击,甚至只能看到数字键盘,连一个标点符号按钮都看不到
  2. 我们希望用户不要去随意的换输入框去输入数字,只能从前往后输入,只能从后往前删除

这样的输入框可能很简单很呆板,但是体验非常好,也避免了不怀好意的用户用出很多问题,因为我们的功能就这么多,功能越多问题越多,那么长痛不如短痛,我们最简单的办法就是学习市面上大多数厂家那样,自定义这整套界面。

实现

那我么实现就分为两个部分,一个是输入框界面,还有一个就是自定义的键盘。

输入框界面思路

  1. 我们根本不需要光标,这会影响我们的原生输入框的弹出和影藏,还会不好控制焦点,那么我们就只需要用TextView来代替
  2. 我们只需要遍历来判断输入框中有没有字,就可以做到按顺序输入的视觉效果
  3. 输入到最后一个的时候可以自动触发发送验证码的操作

键盘界面的思路

1.我们只需要数字0-9这十个数字的输入按钮,以及一个删除按钮
2.我们需要可以弹出隐藏这个键盘,那么底部弹出用popuwindow再好不过了

思路总结

以上便是我提供的所有思路,按照上述描述大部分熟练的人应该可以自己写出来了。

以下贴出部分代码方便大家借鉴

输入框代码

xml部分

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="70dp"
        android:text="请输入6位验证码"
        android:textColor="@color/common_text_color_666" />

    <LinearLayout
        android:id="@+id/input_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:gravity="center">

        <TextView
            android:id="@+id/input1"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input2"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input3"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input4"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input5"
            style="@style/TeamInputStyle" />

        <TextView
            android:id="@+id/input6"
            style="@style/TeamInputStyle"
            android:layout_marginEnd="0dp" />
    </LinearLayout>


</LinearLayout>

style

    <style name="TeamInputStyle">
        <item name="android:layout_width">50dp</item>
        <item name="android:layout_height">50dp</item>
        <item name="android:textSize">30sp</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textColor">@color/textBlack</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_marginEnd">10dp</item>
        <item name="android:background">@drawable/team_number</item>
    </style>

drawble

//正常
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    <corners android:radius="8dp" />
    <solid android:color="#ffffff" />
    <stroke
        android:width="1dp"
        android:color="#AAA" />
</shape>
//红色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    <solid android:color="#ffffff" />
    <corners android:radius="8dp" />
    <stroke
        android:width="1dp"
        android:color="#FF5742" />
</shape>
//灰色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#ffffff" />
    <corners android:radius="8dp" />
    <stroke
        android:width="1dp"
        android:color="#CC000000" />
</shape>

kotlin代码

    override fun afterTextChanged(s: Editable?) {
        if (s?.length == 1) {//这里只监听了最后一个输入框
            val builder = StringBuilder()
            inputs.forEach {
                builder.append(it.text)
            }
            val params = mutableMapOf(
                    Constant.PASS_KEY to builder.toString()
            )
            //然后就可以把这个params拿去做请求了
        }
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    }

    override fun onInput(text: String) {

        for (input in inputs) {
            if (input.text.isEmpty()) {
                input.text = text
                if (input.id == R.id.input1) {
                    inputs.forEach {
                        it.setBackgroundResource(R.drawable.team_number)
                    }
                }
                input.setBackgroundResource(R.drawable.team_number_gray)
                return
            }
        }
    }

    override fun delete() {
        for (input in inputs.asReversed()) {
            if (input.text.isNotEmpty()) {
                input.text = ""
                return
            }
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_xxx)
        inputPop = TeamInputPopupWindow(this, this)
        title_text.text = "输入验证码"
        inputs = listOf(
                input1,
                input2,
                input3,
                input4,
                input5,
                input6
        )
        input6.addTextChangedListener(this)
        input6.post {
            inputPop.showPop()
        }
    }

以下是TeamInputPopupWindow代码

class TeamInputPopupWindow(private val activity: Activity, private val listener: OnInputListener) :
        View.OnClickListener {


    private lateinit var mPopupWindow: PopupWindow

    fun showPop() {
        val view = LayoutInflater.from(activity)
                .inflate(R.layout.team_input, activity.window.decorView as ViewGroup, false)
        //设置view
        setView(view)
        mPopupWindow = PopupWindow(
                view,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        )
        mPopupWindow.isFocusable = true
        mPopupWindow.isOutsideTouchable = true
        mPopupWindow.showAtLocation(view, Gravity.BOTTOM, 0, 0)
//        mPopupWindow.showAtLocation(parent, Gravity.BOTTOM, 0, 0)
    }

    fun dismiss() {
        mPopupWindow.dismiss()
    }

    private fun setView(view: View) {
        view.findViewById<View>(R.id.one).setOnClickListener(this)
        view.findViewById<View>(R.id.two).setOnClickListener(this)
        view.findViewById<View>(R.id.three).setOnClickListener(this)
        view.findViewById<View>(R.id.four).setOnClickListener(this)
        view.findViewById<View>(R.id.five).setOnClickListener(this)
        view.findViewById<View>(R.id.six).setOnClickListener(this)
        view.findViewById<View>(R.id.seven).setOnClickListener(this)
        view.findViewById<View>(R.id.eight).setOnClickListener(this)
        view.findViewById<View>(R.id.nine).setOnClickListener(this)
        view.findViewById<View>(R.id.zero).setOnClickListener(this)
        view.findViewById<View>(R.id.delete).setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.one -> listener.onInput("1")
            R.id.two -> listener.onInput("2")
            R.id.three -> listener.onInput("3")
            R.id.four -> listener.onInput("4")
            R.id.five -> listener.onInput("5")
            R.id.six -> listener.onInput("6")
            R.id.seven -> listener.onInput("7")
            R.id.eight -> listener.onInput("8")
            R.id.nine -> listener.onInput("9")
            R.id.zero -> listener.onInput("0")
            R.id.delete -> listener.delete()
        }
    }
}

interface OnInputListener {
    fun onInput(text: String)
    fun delete()
}

以上便是大部分的片段代码了,根据以上逻辑已经可以完成大部分的效果了。

总结

很多东西其实思路比实现更重要,如果你知道怎么实现会少坑的话,尽量在设计阶段就回避掉这些东西。以上便是如何实现验证码输入框的页面逻辑了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,470评论 1 11
  • (本文来自《Custom Keyboard》)自定义键盘为那些希望体验更新颖的输入法或者需要用到iOS不支持的语言...
    RickMao阅读 7,922评论 3 66
  • 不知不觉,岁寒输入法的更新历史已经可以列出这么一长串来了。从中可以看出,岁寒的发展过程也是一个不断试错的过程,其中...
    临岁之寒阅读 33,901评论 1 6
  • 吃货地图产品需求文档 V1.0-2015/03/30 1概述 1.1产品概述及目标 概述:“吃货地图”是一款基于i...
    michaelshan阅读 5,824评论 1 46
  • 界面是软件与用户交互的最直接的层,界面的好坏决定用户对软件的第一印象。而且设计良好的界面能够引导用户自己完成...
    A梦想才让心跳存在阅读 1,028评论 0 4