Android自定义控件系列,自定义一个特殊的密码输入框

在项目中有涉及到输入密码的地方并且UI已经给了相应的效果图,由于普通的EditTextView已经无法满足要求,所以只能自己造轮子了。
先看一张效果图
在这里插入图片描述
老规矩 还是动手前先理思路
~输入框嘛肯定得处理很多的按键响应事件 系统的EditTextView已经处理的够好了 所以得基于它来扩展
~得把一些输入框的默认样式替换成自己的
~把整个宽度等分成6份并计算出每个框框的x,y的位置并记录下来
~设置监听事件用来获取当前输入的内容和数量
~每当内容变化时动态的绘制成对应框框里的小圆点
~计算下一个要输入的索引位置并绘制出对应的待输入游标

自定义一个SafeEditorTextView类 继承至 AppCompatEditText 并把默认背景和默认游标给去掉

public class SafeEditorTextView extends AppCompatEditText {
	 public SafeEditorTextView(Context context) {
        super(context);
    }

    public SafeEditorTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setBackground(null);
        setCursorVisible(false);
    }

}

定义和初始化所需的对象

//    边框画笔
    private Paint mSide;
//    圆点画笔
    private Paint mCircle;
//    游标线画笔
    private Paint mLine;
//    用户输入的密码
    private String mText = "";
//    输入内容的长度
    private int mLength = 6;
//    框与框的间距
    private int space = 30;
//    边框圆角度数
    private int round = 5;
//    线条宽度
    private int strokeWidth = 3;
//    存储边框临界值的容器
    private List<RectF> rectFList ;
    
    public SafeEditorTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mSide = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSide.setStyle(Paint.Style.STROKE);
        mSide.setStrokeWidth(strokeWidth);

        mCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCircle.setStyle(Paint.Style.FILL);
        mCircle.setColor(Color.BLACK);

        mLine = new Paint();
        mLine.setColor(Color.BLACK);
        mLine.setStyle(Paint.Style.FILL_AND_STROKE);
        mLine.setStrokeWidth(strokeWidth);
        setTextColor(Color.TRANSPARENT);
        setInputType(InputType.TYPE_CLASS_NUMBER |InputType.TYPE_NUMBER_VARIATION_PASSWORD);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(mLength)});
        setBackground(null);
        setCursorVisible(false);
    }

接下来要在onMeasure中计算出每个框框的位置
思路是先获取到当前容器的总宽度supWidth,根据总宽度减去space*mLength-1得出totalSpace总间距的宽度,用supWidth减去totalSpace/mLength得出单个框框的宽度,高度默认使用的是容器的高度也就是supHeight,

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (rectFList == null){
            rectFList = new ArrayList<>();
            int height = getMeasuredHeight();
            int width = getMeasuredWidth();
            int singleWidth = (width - (mLength - 1) * space) / mLength;
        }
    }

有了单个边框的宽高接下来再来计算每个边框所在Rect的位置
假设当前边框的索引是i,i=0时那么边框的left位置就是i*singleWidth+strokeWidth,-、i>0时边框的left位置就是i * singleWidth + i * space,而它的right位置就是i * singleWidth + singleWidth + i * space,接下来看代码

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (rectFList == null){
            rectFList = new ArrayList<>();
            int height = getMeasuredHeight();
            int width = getMeasuredWidth();
            int singleWidth = (width - (mLength - 1) * space) / mLength;
            for (int i = 0; i < mLength; i++) {
                int left = i == 0 ? i * singleWidth + strokeWidth: i * singleWidth + i * space;
                RectF rectF = new RectF(left, strokeWidth, i * singleWidth + singleWidth + i * space, height - strokeWidth);
                rectFList.add(rectF);
            }
        }
    }

计算完边框的rect接下来就要进行绘制了
先根据输入的length来设置不同边框的颜色,在将已经输入的文字使用圆来代替,最后绘制出下一个要输入的游标位置

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        Log.d(TAG, "onDraw: " + mText.length());
        for (int i = 0; i < mLength; i++) {
            RectF rectF = rectFList.get(i);
//            设置需要高亮的边框颜色
            if (i <= mText.length()){
                mSide.setColor(Color.BLACK);
            }else {
                mSide.setColor(Color.LTGRAY);
            }
//            绘制边框
            canvas.drawRoundRect(rectF, round,round, mSide);

            float singleWidth = rectF.right - rectF.left;
            float singleHeight = rectF.bottom - rectF.top;
//            将已经输入的文字用圆代替
            if (i < mText.length()){
                canvas.drawCircle(rectF.left + singleWidth / 2, rectF.bottom - singleHeight / 2, singleWidth / 6, mCircle);
            }

            //            绘制游标线
            if (i == mText.length()){
                float lineScale = singleWidth / 4;
                float startY = rectF.top  + singleHeight / 4 * 3;
                canvas.drawLine(rectF.left + lineScale, startY, rectF.right - lineScale, startY, mLine);
            }
        }
    }

到这基本上就已经初具雏形了,剩下的要定义一个回调给使用者用来监听输入内容和状态使用

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        if (text.length() <= mLength){
            mText = text.toString();
            if (changeListener != null) changeListener.onChange(mText);
        }

        if (text.length() == mLength){
            if (changeListener != null) changeListener.onFinish(mText);
        }
    }
    public void setInputChangeListener(OnInputChangeListener changeListener) {
        this.changeListener = changeListener;
    }

    public interface OnInputChangeListener{
        void onFinish(@NonNull String text);
        void onChange(@NonNull String text);
    }

最后将最长长度mLength、间距space、线宽strokeWidth这些变量定义成自定义属性让使用者在xml中可以进行设置

    <declare-styleable name="SafeEditorTextView">
        <attr name="lenth" format="integer"/>
        <attr name="android:strokeWidth"/>
        <attr name="space" format="dimension"/>
    </declare-styleable>

最后的最后贴下源码


/**
 * 自定义密码输入框
 */
public class SafeEditorTextView extends AppCompatEditText {
    public SafeEditorTextView(Context context) {
        super(context);
    }

    public SafeEditorTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SafeEditorTextView);
        mLength = array.getInt(R.styleable.SafeEditorTextView_lenth, 6);
        strokeWidth = array.getDimensionPixelOffset(R.styleable.SafeEditorTextView_android_strokeWidth, 3);
        space = array.getDimensionPixelOffset(R.styleable.SafeEditorTextView_space, 30);
        array.recycle();

        mSide = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSide.setStyle(Paint.Style.STROKE);
        mSide.setStrokeWidth(strokeWidth);

        mCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCircle.setStyle(Paint.Style.FILL);
        mCircle.setColor(Color.BLACK);

        mLine = new Paint();
        mLine.setColor(Color.BLACK);
        mLine.setStyle(Paint.Style.FILL_AND_STROKE);
        mLine.setStrokeWidth(strokeWidth);
        setTextColor(Color.TRANSPARENT);
        setInputType(InputType.TYPE_CLASS_NUMBER |InputType.TYPE_NUMBER_VARIATION_PASSWORD);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(mLength)});
        setBackground(null);
        setLongClickable(false);
        setTextIsSelectable(false);
        setCursorVisible(false);
        Log.d(TAG, "SafeEditorTextView: ");
    }
//    边框画笔
    private Paint mSide;
//    圆点画笔
    private Paint mCircle;
//    游标线画笔
    private Paint mLine;
//    用户输入的密码
    private String mText = "";
//    最长长度
    private int mLength = 6;
//    框与框的间距
    private int space = 30;
//    边框圆角度数
    private int round = 5;
//    线条宽度
    private int strokeWidth = 3;
//    存储边框临界值的容器
    private List<RectF> rectFList ;
    private static final String TAG = "SafeEditorTextView";
//    输入变化的监听器
    private OnInputChangeListener changeListener;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (rectFList == null){
            rectFList = new ArrayList<>();
            int height = getMeasuredHeight();
            int width = getMeasuredWidth();
            int singleWidth = (width - (mLength - 1) * space) / mLength;
            for (int i = 0; i < mLength; i++) {
                int left = i == 0 ? i * singleWidth + strokeWidth: i * singleWidth + i * space;
                RectF rectF = new RectF(left, strokeWidth, i * singleWidth + singleWidth + i * space, height - strokeWidth);
                rectFList.add(rectF);

            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        Log.d(TAG, "onDraw: " + mText.length());
        for (int i = 0; i < mLength; i++) {
            RectF rectF = rectFList.get(i);
//            设置需要高亮的边框颜色
            if (i <= mText.length()){
                mSide.setColor(Color.BLACK);
            }else {
                mSide.setColor(Color.LTGRAY);
            }
//            绘制边框
            canvas.drawRoundRect(rectF, round,round, mSide);

            float singleWidth = rectF.right - rectF.left;
            float singleHeight = rectF.bottom - rectF.top;
//            将已经输入的文字用圆代替
            if (i < mText.length()){
                canvas.drawCircle(rectF.left + singleWidth / 2, rectF.bottom - singleHeight / 2, singleWidth / 6, mCircle);
            }

//            绘制游标线
            if (i == mText.length()){
                float lineScale = singleWidth / 4;
                float startY = rectF.top  + singleHeight / 4 * 3;
                canvas.drawLine(rectF.left + lineScale, startY, rectF.right - lineScale, startY, mLine);
            }
        }
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        if (text.length() <= mLength){
            mText = text.toString();
            if (changeListener != null) changeListener.onChange(mText);
        }

        if (text.length() == mLength){
//            hideKeyboard();
            if (changeListener != null) changeListener.onFinish(mText);
        }
    }

    //隐藏软键盘并让editText失去焦点
    private void hideKeyboard() {
        clearFocus();
        //这里先获取InputMethodManager再调用他的方法来关闭软键盘
        //InputMethodManager就是一个管理窗口输入的manager
        InputMethodManager im = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (im != null) {
            im.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

    public void setInputChangeListener(OnInputChangeListener changeListener) {
        this.changeListener = changeListener;
    }

    public interface OnInputChangeListener{
        void onFinish(@NonNull String text);
        void onChange(@NonNull String text);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值