开发者

Android开发自定义短信验证码实现过程详解

开发者 https://www.devze.com 2023-11-19 13:05 出处:网络 作者: 龙之音
目录效果图简介功能分析代码实现1、创建CodeEditText2、创建自定义配置参数3、获取自定义配置参数4、重写 onDraw 方法对当前焦点索引currentIndex的计算单个验证码宽度codeItemWidth的计算第一个验证码距离左边的间距
目录
  • 效果图
  • 简介
  • 功能分析
  • 代码实现
    • 1、创建CodeEditText
    • 2、创建自定义配置参数
    • 3、获取自定义配置参数
    • 4、重写 onDraw 方法
      • 对当前焦点索引currentIndex的计算
      • 单个验证码宽度codeItemWidth的计算
      • 第一个验证码距离左边的间距space的计算
    • 5、绘制表格样式
      • 6、绘制方块样式
        • 7、绘制横线样式
          • 8、绘制圈圈样式
            • 9、绘制输入数据展示
              • 10、控件使用

              效果图

              Android开发自定义短信验证码实现过程详解

              简介

              基本上只要需要登录的APP,都会有验证码输入,所以说是比较常用的控件,而且花样也是很多的,这里列android出来4种样式,分别是:

              • 表格类型
              • 方块类型
              • 横线类型
              • 圈圈类型

              其实还有很多其他的样式,但是这四种是我遇到最多的样式,所以特地拿来实现下,网上有很多类似的轮子,实现方式也是蛮多的,比如说:

              • 组合控件(线性布局添加子View)
              • 自定义ViewGrop
              • 自定义View
              • ...

              自己看了些网络上实现的方案,参考了一些比较好的方式,这里先来分析下这个控件有哪些功能,再决定实现方案。

              功能分析

              • 1、默认状态样式展示
              • 2、支持设置最大数量
              • 3、支持4种类型样式
              • 4、点击控件,弹出键盘,获取焦点,显示焦点样式。焦点失去,展示默认样式。
              • 5、输入数据,焦点移动到下一个位置,删除数据,焦点也跟随移动

              通过功能4让我第一想到的就是EditText控件,那么怎么做呢?大家都知道EditText有自己的样式和操作,如果我们可以屏蔽无用的样式和功能,留下我们需要的不就可以了吗。python

              [图片上传失败...(image-888083-1663139918159)]

              EditText

              • 点击EditText可以弹出键盘(需要的),并获取焦点(需要的),显示光标(不需要的)
              • 长按EditText会显示复制,粘贴等操作(不需要的)
              • 输入数据,内容默认显示(不需要的)

              上面对EditText基本使用时出现的样式和操作,有的是需要的,有的是不需要的,我们可以对不需要的进行屏蔽,来代码走起。

              代码实现

              1、创建CodeEditText

              继承AppCompatEditText,并屏蔽一些功能。

              class CodeEditText @JvmOverloads constructor(context: Context, var attrs: AttributeSet, var defStyleAttr: Int = 0) :
                  AppCompatEditText(context, attrs, defStyleAttr) {
                  init {
                      initSetting()
                  }
                  private fun initSetting() {
                      //内容默认显示(不需要的)- 文字设置透明
                      setTextColor(Color.TRANSPARENT)
                      //触摸获取焦点
                      isFocusableInTouchMode = true
                      //不显示光标
                      isCursorVisible = false
                      //屏蔽长按操作
                      setOnLongClickListener { true }
                  }
              }
              

              2、创建自定义配置参数

              这里根据样式,列举一些参数,如果需要其他参数可以自行添加

               <declare-styleable name="CodeEditText">
                      <!--code模式-->
                      <attr name="code_mode" format="enum">
                          <!--文字-->
                          <enum name="text" value="0" />
                          <!--TODO 拓展-->
                      </attr>
                      <!--code样式-->
                      <attr name="code_style" format="enum">
                          <!--表格-->
                          <enum name="form" value="0" />
                          <!--方块-->
                          <enum name="rectangle" value="1" />
                          <!--横线-->
                          <enum name="line" value="2" />
                          <!--圆形-->
                          <enum name="circle" value="3" />
                          <!--TODO 拓展-->
                      </attr>
                      <!--code背景色-->
                      <attr name="code_bg_color" format="color" />
                      <!--边框宽度-->
                      <attr name="code_border_width" format="dimension" />
                      <!--边框默认颜色-->
                      <attr name="code_border_color" format="color" />
                      <!--边框选中颜色-->
                      <attr name="code_border_select_color" format="color" />
                      <!--边框圆角-->
                      <attr name="code_border_radius" format="dimension" />
                      <!--code 内容颜色(密码或文字)-->
                      <attr name="code_content_color" format="color" />
                      <!--code 内容大小(密码或文字)-->
                      <attr name="code_content_size" format="dimension" />
                      <!--code 单个宽度-->
                      <attr name="code_item_width" format="dimension" />
                      <!--code Item之间的间隙-->
                      <attr name="code_item_space" format="dimension" />
                  </declare-styleable>

              3、获取自定义配置参数

              这里获取参数,有的参数默认给了默认值。

                  private fun initAttrs() {
                      val obtainStyledAttributes = context.obtainStyledAttributes(attrs, R.styleable.CodeEditText)
                      codeMode = obtainStyledAttributes.getInt(R.styleable.CodeEditText_code_mode, 0)
                      codeStyle = obtainStyledAttributes.getInt(R.styleable.CodeEditText_code_style, 0)
                      borderWidth = obtainStyledAttributes.getDimension(Randroid.styleable.CodeEditText_code_border_width, DensityUtil.dip2px(context, 1.0f))
                      borderColor = obtainStyledAttributes.getColor(R.styleable.CodeEditText_code_border_color, Color.GRAY)
                      borderSelectColor = obtainStyledAttributes.getColor(R.styleable.CodeEditText_code_border_select_color, Color.GRAY)
                      borderRadius = obtainStyledAttributes.getDimension(R.styleable.CodeEditText_code_border_radius, 0f)
                      codeBgColor = obtainStyledAttributes.getColor(R.styleable.CodeEditText_code_bg_color, Color.WHITE)
                      codeItemWidth = obtainStyledAttributes.getDimension(R.styleable.CodeEditText_code_item_width, -1f).toInt()
                      codeItemSpace = obtainStyledAttributes.getDimension(R.styleable.CodeEditText_code_item_space, DensityUtil.dip2px(context, 16f))
                      if (codeStyle == 0) codeItemSpace = 0f
                      codeContentColor = obtainStyledAttributes.getColor(R.styleable.CodeEditText_code_content_color, Color.GRAY)
                      codeContentSize = obtainStyledAttributes.getDimension(R.styleable.CodeEditText_code_content_size, DensityUtil.dip2px(context, 16f))
                      obtainStyledAttributes.recycle()
                  }

              4、重写 onDraw 方法

                 override fun onDraw(canvas: Canvas) {
                      super.onDraw(canvas)
                      //当前索引(待输入的光标位置)
                      currentIndex = text?.length ?: 0
                      //Item宽度(这里判断如果设置了宽度并且合理就使用当前设置的宽度,否则平均计算)
                      codeItemWidth = if (codeItemWidth != -1 && (codeItemWidth * maxLength + codeItemSpace * (maxLength - 1)) <= measuredwidth) {
                          codeItemWidthhttp://www.devze.com
                      } else {
                          ((measuredWidth - codeItemSpace * (maxLength - 1)) / maxLength).toInt()
                      }
                      //计算左右间距大小
                      space = ((measuredWidth - codeItemWidth * maxLength - codeItemSpace * (maxLength - 1)) / 2).toInt()
                      //绘制Code样式
                      when (codeStyle) {
                          //表格
                          0 -> {
                              drawFormCode(canvas)
                          }
                          //方块
                          1 -> {
                              drawRectangleCode(canvas)
                          }
                          //横线
                          2 -> {
                              drawLineCode(canvas)
                          }
                          //圆形
                          3 -> {
                              drawCircleCode(canvas)
                          }
                      }
                      //绘制文字
                      drawContentText(canvas)
                  }

              在onDraw方法中主要是根据设置的codeStyle样式,绘制不同的样子。在绘制之前,主要做了三个操作。

              • 对当前焦点索引currentIndex的计算
              • 单个验证码宽度codeItemWidth的计算
              • 第一个验证码距离左边的间距space的计算

              对当前焦点索引currentIndex的计算

              这里巧妙的使用了获取当前EditText数据的长度作为当前索引值,比如说,开始没有输入数据,获取长度为0,则当前焦点应该在0索引位置上,当输入一个数据时,数据长度为1,则焦点变为1,焦点相当于移动到了索引1的位置上,删除数据同理,这样就达到了上面分析的 ”功能5“的效果。

              单个验证码宽度codeItemWidth的计算

              这里因为有4中样式,有的是表格一体展示,有的是分开展示,比如方块、横线、圈圈,这三种中间是有空隙的,这个空隙大小我们做了配置参数code_item_space,对于这个参数,表格样式是不需要的,所以不管你设置了还是没有设置,在表格样式中是无效的。所以这里做了统一计算。

              第一个验证码距离左边的间距space的计算

              因为需要绘制,所以需要起始点,那么起点应该是:(控件总宽度-所有验证码的宽度-所有验证码之前的空隙)/2 .

              5、绘制表格样式

                  /**
                   * 表格code
                   */
                  private fun drawFormCode(canvas: Canvas) {
                      //绘制表格边框
                      defaultDrawable.setBounds(space, 0, measuredWidth - space, measuredHeight)
                      defaultBitmap = CodeHelper.drawableToBitmap(defaultDrawable, measuredWidth - 2 * space, measuredHeight)
                      canvas.drawBitmap(defaultBitmap!!, space.toFloat(), 0f, mLinePaint)
                      //绘制表格中间分割线
                      for (i in 1 until maxLength) {
                          val startX = space + codeItemWidth * i + codeItemSpace * i
                          val startY = 0f
                          val stopY = measuredHeight
                          canvas.drawLine(startX, startY, startX, stopY.toFloat(), mLinePaint)
                      }
                      //绘制当前位置边框
                      for (i in 0 until maxLength) {
                          if (currentIndex != -1 && currentIndex == i && isCodeFocused) {
                              when (i) {
                                  0 -> {
                                      val radii = floatArrayOf(borderRadius, borderRadius, 0f, 0f, 0f, 0f, borderRadius, borderRadius)
                                      currentDrawable.cornerRadii = radii
                                      currentBitmap =
                                          CodeHelper.drawableToBitmap(currentDrawable, (codeItemWidth + borderWidth / 2).toInt(), measuredHeight)
                                  }
                                  maxLength - 1 -> {
                                      val radii = floatArrayOf(0f, 0f, borderRadius, borderRadius, borderRadius, borderRadius, 0f, 0f)
                                      currentDrawable.cornerRadii = radii
                                      currentBitmap =
                                          CodeHelper.drawableToBitmap(currentDrawable, (codeItemWidth + borderWidth / 2 + codeItemSpace).toInt(), measuredHeight)
                                  }
                                  else -> {
                                      val radii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
                                      currentDrawable.cornerRadii = radii
                                      currentBitmap = CodeHelper.drawableToBitmap(currentDrawable, (codeItemWidth + borderWidth).toInt(), measuredHeight)
                                  }
                              }
                              val left = if (i == 0) (space + codeItemWidth * i) else ((space + codeItemWidth * i + codeItemSpace * i) - borderWidth / 2)
                              canvas.drawBitmap(currentBitmap!!, left.toFloat(), 0f, mLinePaint)
                          }
                      }
                  }

              6、绘制方块样式

               /**
                   * 方块 code
                   */
                  private fun drawRectangleCode(canvas: Canvas) {
                      defaultDrawable.cornerRadius = borderRadius
                      defaultBitmap = CodeHelper.drawableToBitmap(defaultDrawable, codeItemWidth, measuredHeight)
                      currentDrawable.cornerRadius = borderRadius
                      currentBitmap = CodeHelper.drawableToBitmap(currentDrawable, codeItemWidth, measuredHeight)
                      for (i in 0 until maxLength) {
                          val left = if (i == 0) {
                              space + i * codeItemWidth
                          } else {
                              space + i * codeItemWidth + codeItemSpace * i
                          }
                          //当前光标样式
                          if (currentIndex != -1 && currentIndex == i && isCodeFocused) {
                              canvas.drawBitmap(currentBitmap!!, left.toFloat(), 0f, mLinePaint)
                          }
                          //默认样式
                          else {
                              canvas.drawBitmap(defaultBitmap!!, left.toFloat(), 0f, mLinePaint)
                          }
                      }
                  }

              7、绘制横线样式

               /**
                   * 横线 code
                   */
                  private fun drawLineCode(canvas: Canvas) {
                      for (i in 0 until maxLength) {
                          //当前选中状态
                          if (currentIndex == i && isCodeFocused) {
                              mLinePaint.color = borderSelectColor
                          }
                          //默认状态
                          else {
                              mLinePaint.color = borderColor
                          }
                          val startX: Float = space + codeItemWidth * i + codeItemSpace * i
                          val startY: Float = measuredHeight - borderWidth
                          val stopX: Float = startX + codeItemWidth
                          val stopY: Float = startY
                          canvas.drawLine(startX, startY, stopX, stopY, mLinePaint)
                      }
                  }

              8、绘制圈圈样式

               /**
                   * 圆形 code
                   */
                  private fun drawCircleCode(canvas: Canvas) {
                      for (i in 0 until maxLength) {
                          //当前绘制的圆圈的左x轴坐标
                          var left: Float = if (i == 0) {
                              (space + i * codeItemWidth).toFloat()
                          } else {
                              space + i * codeItemWidth + codeItemSpace * i
                          }
                          //圆心坐标
                          val cx: Float = left + codeItemWidth / 2f
                          val cy: Float = measuredHeight / 2f
                          //圆形半径
                          val radius: Float = codeItemWidth / 5f
                          //默认样式
                          if (i >= currentIndex) {
                              canvas.drawCircle(cx, cy, radius, mLinePaint.apply { style = Paint.Style.FILL })
                          }
                      }
                  }

              9、绘制输入数据展示

                  /**
                   * 绘制内容
                   */
                  private fun drawContentText(canvas: Canvas) {
                      val textStr = tejsxt.toString()
                      for (i in 0 until maxLength) {
                          if (textStr.isNotEmpty() && i < textStr.length) {
                              when (codeMode) {
                                  //文字
                                  0 -> {
                                      val code: String = textStr[i].toString()
                                      val textWidth: Float = mTextPaint.measureText(code)
                                      val textHeight: Float = CodeHelper.getTextHeight(code, mTextPaint)
                                      val x: Float = space + codeItemWidth * i + codeItemSpace * i + (codeItemWidth - textWidth) / 2
                                      val y: Float = (measuredHeight + textHeight) / 2f
                                      canvas.drawText(code, x, y, mTextPaint)
                                  }
                                  //TODO 拓展
                              }
                          }
                      }
                  }

              上面就是对四种样式的绘制,主要考察的API如下:

              • canvas.drawBitmap()
              • canvas.drawLine()
              • canvas.drawCircle()
              • canvas.drawText()

              主要对这四个API的使用数据上的计算,相对比较的简单,其中有个点击获取焦点以及失去焦点更新样式方式:

                override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
                      super.onFocusChanged(focused, direction, previouslyFocusedRect)
                      isCodeFocused = focused
                      invalidate()
                  }
              

              通过isCodeFocused字段来控制。

              10、控件使用

                <com.yxlh.androidxy.demo.ui.codeet.widget.CodeEditText
                      android:layout_width="match_parent"
                      android:layout_height="50dp"
                      android:layout_marginLeft="20dp"
                      android:layout_marginTop="40dp"
                      android:layout_marginRight="20dp"
                      android:inputType="number"
                      android:maxLength="4"
                      app:code_border_color="@android:color/darker_gray"
                      app:code_border_radius="5dp"
                      app:code_border_select_color="@color/design_default_color_primary"
                      app:code_border_width="2dp"
                      app:code_content_color="@color/purple_500"
                      app:code_content_size="35dp"
                      app:code_item_width="50dp"
                      app:code_mode="text"
                      app:code_bg_color="#E1E1E1"
                      app:code_style="rectangle" />

              github链接:https://github.com/yixiaolunhui/AndroidXY

              以上就是Android开发自定义短信验证码实现过程详解的详细内容,更多关于Android自定义短信验证码的资料请关注我们其它相关文章!

              0

              精彩评论

              暂无评论...
              验证码 换一张
              取 消

              关注公众号