android自定义 验证码框 密码框

  在写app的时候,需要一个输入验证码,数字框的场景。没有找到合适的,就动手写了一个。源码下载)

先上效果图

实现思路

利用recyclerView+editText,RecyclerView 能够很轻松的布局横向,纵向,网格,顺序反转。以后扩张会很轻松。EditText能很方便的管理焦点接收键盘数据。

开车上路

原始的recyclerView肯定不行,因为要把一些处理逻辑封装在里面。所以我们先创建一个子类。

class NumberRecyclerView :RecyclerView{
    constructor(context:Context,attributeSet: AttributeSet):super(context,attributeSet)
    constructor(context: Context,attributeSet: AttributeSet,defStyle:Int):super(context,attributeSet,defStyle)
}

收入框基本都是全部显示的,所有先设置一下recyclerview全部撑开,这样外面嵌套任何滑动控件都无惧了。在NumberRecycleriView里覆写onMeasure()

override fun onMeasure(widthSpec: Int, heightSpec: Int) {
    //measureSpec 前两位bit代表SpecMode 后30位代表size。所以下边的expandWidthSpec的模式为子view想要多大就有多大
    val expandWidthSpec = MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2,MeasureSpec.AT_MOST)
    super.onMeasure(expandWidthSpec, heightSpec)
}

这个是横向全部展开,主要利用measureSpec机制。纵向的只需要把height的measureSpec这样设置就可以。

配置RecyclerView

先配上recyclerView的三剑客。因为是自己内部使用,保证封装,搞几个内部类

inner class NumberAdapter : RecyclerView.Adapter<NumberViewHolder>(){
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NumberViewHolder {
        return NumberViewHolder(LayoutInflater.from(context).inflate(R.layout.item_number_input,parent,false))
    }

    override fun getItemCount(): Int = count


    override fun onBindViewHolder(holder: NumberViewHolder, position: Int) {
        holder.setData(position)
    }

}

inner class NumberViewHolder(view:View):RecyclerView.ViewHolder(view),View.OnFocusChangeListener{
    init {
        itemView.onFocusChangeListener = this
        (itemView as EditText).showSoftInputOnFocus = false
    }

    override fun onFocusChange(v: View?, hasFocus: Boolean) {
        if (hasFocus){
            current = layoutPosition
        }
    }

    fun setData(position: Int){
        if (current+1 == position && changeFocusablePosition){
            changeFocusablePosition = false
            Looper.myQueue().addIdleHandler { itemView.requestFocus()
            false}
        }
        (itemView as EditText).setText(datas[position]?.toString()?:"")
    }
}

inner class SimpleItemDecoration(private val top:Int = 0,private val end:Int = 0,
                                 private val bottom:Int = 0,private val start:Int = 0):ItemDecoration(){
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State) {
        outRect.set(start,top,end,bottom)
    }
}

三剑客有啦,我们直接用上。recyclerview就武装完毕啦。

init {
    layoutManager = LinearLayoutManager(context,LinearLayoutManager.HORIZONTAL,true)
    adapter =NumberAdapter()
    addItemDecoration(SimpleItemDecoration(0,2,0,2))
}

在构造方法里,直接设置好。接下来就是配置一些业务逻辑里。

肯定会有位数限制,那我们准备给count字段

var count = 0
   set(value) {
       field = value
       adapter?.notifyDataSetChanged()
   }

重写一下set方法,count的每次改变都需要重新布局一下recyclerview,所以直接用adapter通知更改。

还需要存储adapter的数据,现在adapter这层已经被我们封装隐藏了,所有我们把数据直接存在view层

override fun setAdapter(adapter: Adapter<*>?) {
    if (adapter !is NumberAdapter){
        throw IllegalArgumentException("NumberRecyclerView only      use NumberAdapter!")
    }
    super.setAdapter(adapter)
}

private var current:Int = 0
@SuppressLint("UseSparseArrays")
private val datas = SparseArray<Char>()

数据载体有了,那我们再提供个方法,让外边可以设置每个条目的数据,整个架构就完成了。

fun setIndexData(index:Int,data:Char){
    if (index >= count){
        throw IndexOutOfBoundsException("count is $count, index is $index")
    }
    datas.put(index,data)
    considerChange()
}

fun setData(data: Char){
    datas.put(current,data)
    //只有true时需要改变focus
    considerChange()
}
完善业务逻辑

我是使用自定义的键盘,所有在EditText初始化的时候设置不弹出软键盘

init {
            itemView.onFocusChangeListener = this
            (itemView as EditText).showSoftInputOnFocus = false
}
override fun onFocusChange(v: View?, hasFocus: Boolean) {
            if (hasFocus){
                current = layoutPosition
            }
}

如果客官们用系统软件盘,那么editText.setKeyListener 监听输入来控制焦点就可以。

每输入一位,改遍item的焦点,让下次输入正确显示

fun setData(position: Int){
    if (current+1 == position && changeFocusablePosition){
        changeFocusablePosition = false
        Looper.myQueue().addIdleHandler { itemView.requestFocus()
        false}
    }
    (itemView as EditText).setText(datas[position]?.toString()?:"")
}

//在view里面添加方法,该方法在setData中调用
private fun considerChange() {
        changeFocusablePosition = true
        adapter?.notifyDataSetChanged()
        notifyDataChanged()
    }

移动焦点改变背景,提醒用户现在输入的是哪个位子,这个直接用selector实现

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatEditText xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:background="@drawable/select_input_number"
    android:cursorVisible="false"
    android:layout_width="35dp"
    android:layout_height="35dp">

</androidx.appcompat.widget.AppCompatEditText>

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/shape_corner_8_stroke_primary" android:state_focused="true" />
    <item android:drawable="@drawable/shape_corner_8_stroke_black" />
</selector>
使用

activity布局

<com.yang.numberinput.NumberRecyclerView
    android:id="@+id/number"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

设置数据

number.count = 6
btn_input.setOnClickListener {
    number.setData(48.toChar())
}
细节扩展
  • 边框颜色

  • 字体大小

  • 方向设置

    等等。。。有需要的客官可以下载源码自己扩展,如有疑问也可以联系我探讨一下。谢谢阅读。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android自定义相机取景可以通过自定义View来实现。具体步骤如下: 1.在布局文件中添加SurfaceView和自定义View,其中SurfaceView用于相机预览,自定义View用于绘制取景。 2.在Activity中获取SurfaceView和自定义View的实例,并在SurfaceView的回调函数中打开相机。 3.在自定义View的onDraw()方法中绘制取景。 4.在Activity中实现相机拍照的回调函数,并在回调函数中获取拍照的图片。 5.在自定义View中添加手势监听器,实现取景的移动和缩放功能。 代码示例: ``` //布局文件 <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.example.myapp.MyView android:id="@+id/myView" android:layout_width="match_parent" android:layout_height="match_parent" /> //Activity中的代码 public class CameraActivity extends Activity implements SurfaceHolder.Callback { private SurfaceView mSurfaceView; private MyView mMyView; private Camera mCamera; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView); mMyView = (MyView) findViewById(R.id.myView); SurfaceHolder holder = mSurfaceView.getHolder(); holder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { mCamera = Camera.open(); try { mCamera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } mCamera.startPreview(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mCamera.stopPreview(); Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(width, height); mCamera.setParameters(parameters); mCamera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); } private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { //获取拍照的图片 } }; } //自定义View中的代码 public class MyView extends View { private Rect mRect = new Rect(100, 100, 500, 500); private Paint mPaint = new Paint(); public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5); } @Override protected void onDraw(Canvas canvas) { canvas.drawRect(mRect, mPaint); } @Override public boolean onTouchEvent(MotionEvent event) { //实现取景的移动和缩放功能 return true; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值