自定义view【练习】之手写密码输入框

本文详细介绍了一种自定义密码输入框的实现方法,包括不同形状的输入框、输入状态的切换、密码显示方式的选择及输入光标的控制。通过继承View和LinearLayout,实现了灵活的密码输入体验。

在这里插入图片描述

package com.yoostar.huayuVOD.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;


/**
 * ================================================
 * 作    者:zhoujianan
 * 版    本:v1.0
 * 创建日期:2019/4/18
 * 描    述:
 * 修订历史:
 * ================================================
 */
public class PasswordView extends View {
    private Paint mPaint;
    private int mPwdEncryptionType;//显示密码的形状  0 圆心类型, 1 *
    private int mPwdEncryptionColor;//显示密码的颜色
    private boolean isDrawPwdEncryption;// 当前输入框是否显示密码
    private int mBoxType;//1 圆形的输入框,2 横线类型的输入框 默认为矩形的输入框
    private int mInputBoxBgColor;
    private int mBoxWidth = 40;
    private int mBoxHeight = 40;
    private boolean isInutState;//当前是否为输入状态
    private boolean isShowInputCursor;//当前输入框是否显示输入光标
    private boolean isDrawInputCursor;//是否绘制输入光标的提示线
    private int inputCursorColor;//设置输入光标的颜色
    private int mTextSize;//输入的字体大小
    private int mBoxStrokeWidth;
    private Context mContext;
    private String inputText;
    private static final String TAG = "PasswordView";

    public void setInputText(String inputText) {
        this.inputText = inputText;
    }

    public void setPwdEncryptionType(int pwdEncryptionType) {
        mPwdEncryptionType = pwdEncryptionType;
    }

    public void setPwdEncryptionColor(int pwdEncryptionColor) {
        mPwdEncryptionColor = pwdEncryptionColor;
    }

    public void setBoxType(int boxType) {
        mBoxType = boxType;
    }

    public void setInputBoxBgColor(int inputBoxBgColor) {
        mInputBoxBgColor = inputBoxBgColor;
    }

    public void setBoxWidth(int boxWidth) {
        mBoxWidth = boxWidth;
    }

    public void setBoxHeight(int boxHeight) {
        mBoxHeight = boxHeight;
    }

    public void setShowInputCursor(boolean showInputCursor) {
        isShowInputCursor = showInputCursor;
    }

    public void setInputCursorColor(int inputCursorColor) {
        this.inputCursorColor = inputCursorColor;
    }

    public void setTextSize(int textSize) {
        mTextSize = textSize;
    }

    public void setBoxStrokeWidth(int boxStrokeWidth) {
        mBoxStrokeWidth = boxStrokeWidth;
    }

    public PasswordView(Context context) {
        this(context, null);
    }

    public PasswordView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//设置防锯齿抖动
        mPaint.setStrokeWidth(mBoxStrokeWidth);
        mPaint.setPathEffect(new CornerPathEffect(1));//设置线条交汇角效果
    }

   /* public PasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }*/

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int width = 0;
        int height = 0;
        //如果是精准测量,直接返回精准值
        if (modeWidth == MeasureSpec.EXACTLY) {
            width = sizeWidth;
        }
        //如果是最大值测量,则返回最小值,防止超出父类控件的最大值
        else if (modeWidth == MeasureSpec.AT_MOST) {
            width = Math.min(sizeWidth, mBoxWidth);
        } else {
            width = mBoxWidth;
        }
        if (modeHeight == MeasureSpec.EXACTLY) {
            height = sizeHeight;
        } else if (modeHeight == MeasureSpec.AT_MOST) {
            height = Math.min(mBoxHeight, sizeHeight);
        } else {
            height = mBoxHeight;
        }
        setMeasuredDimension(width, height);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制输入框
        drawInputBox(canvas);
        //绘制输入框光标
        drawInputCursor(canvas);
        //绘制显示密码样式
        drawPwdType(canvas);
    }

    private void drawPwdType(Canvas canvas) {
        //有输入密码则显示加密图案,没有则清空
        if (isDrawPwdEncryption) {
            Log.i(TAG, "drawPwdType: ");
            //            mPaint.setColor(ContextCompat.getColor(mContext, mPwdEncryptionColor));
            mPaint.setColor(Color.parseColor("#4DFFFFFF"));
            mPaint.setStyle(Paint.Style.FILL);
            switch (mPwdEncryptionType) {
                case 0:
                    canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 8, mPaint);
                    break;
                case 1:
                    mPaint.setTextSize(getMeasuredWidth() / 2 + 10);
                    float stringWidth = mPaint.measureText("*");
                    float baseY = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + stringWidth / 3;
                    float baseX = getMeasuredWidth() / 2 - stringWidth / 2;
                    canvas.drawText("x", baseX, baseY, mPaint);
                    break;
                case 2:
                    mPaint.setTextSize(mTextSize);
                    float inputTextWidth = mPaint.measureText(inputText);
                    float baseInputTextY = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + inputTextWidth / 5;
                    float baseInputTextX = getMeasuredWidth() / 2 - inputTextWidth / 2;
                    canvas.drawText(inputText, baseInputTextX, baseInputTextY, mPaint);
                    break;
                default:
                    break;
            }
        }
    }

    private void drawInputCursor(Canvas canvas) {
        if (isDrawInputCursor && isShowInputCursor) {
            int lineHeight = getMeasuredWidth() / 2 - 5;
            lineHeight = lineHeight < 0 ? getMeasuredWidth() / 2 : lineHeight;
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(Color.parseColor("#FFFFFF"));
            canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineHeight / 2, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineHeight / 2, mPaint);
        }
    }

    private void drawInputBox(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.parseColor("#4475AE"));
        //设置实心的输入框
        switch (mBoxType) {
            case 1:
                canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2 - 5, mPaint);
                break;
            case 2:
                canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), mPaint);
                break;
            default:
                RectF rectF = new RectF(0, 9, getMeasuredWidth(), getMeasuredHeight());
                canvas.drawRoundRect(rectF, 6, 6, mPaint);
        }
    }

    /**
     * 设置即将输入的文本框的状态
     */
    public void drawInputPwd() {
        removeCallbacks(flashCursorRunnable);
        isShowInputCursor = false;
        isDrawPwdEncryption = true;
        invalidate();
    }

    public void clearAllPwd() {
        removeCallbacks(flashCursorRunnable);
        isShowInputCursor = false;
        isDrawPwdEncryption = false;
        invalidate();
    }

    /**
     * 处理当前文本框的输入状态
     */
    public void showInputStatus() {
        isShowInputCursor = true;
        isDrawPwdEncryption = false;
        post(flashCursorRunnable);
    }

    /**
     * 控制闪烁
     */
    Runnable flashCursorRunnable = new Runnable() {
        @Override
        public void run() {
            //控制闪烁
            isDrawInputCursor = !isDrawInputCursor;
            invalidate();
            postDelayed(this, 800);
        }
    };

}


package com.yoostar.huayuVOD.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;

import com.yoostar.huayuVOD.R;
import com.yoostar.huayuVOD.utils.Logger;

import java.util.ArrayList;
import java.util.List;

/** 
 * ================================================
 * 作    者:zhoujianan
 * 版    本:v1.0
 * 创建日期:2019/4/18
 * 描    述:
 * 修订历史:
 * ================================================
 */

/**
 * 技术点:
 * 更新每一个view的显示状态:待输入(显示闪烁光标)输入显示(明文/密码)清除输入
 */
public class PasswordLayout extends LinearLayout {
    private Context mContext;
    private int mRemindLineColor;  //提示输入线颜色
    private int mInputTextColor;  //输入后文字图案提示颜色
    private static final String TAG = "PasswordLayout";

    private int mBoxDrawType = 0;//盒子图画类型 0 矩形 2圆形 3横线
    private int mShowPassType = 0;// 0 提示图案为实心圆 1提示图案为*
    private int mBoxWidth = 40;
    private int mBoxHeight = 40;
    private int mDrawTxtSize = 18;
    private int mCurIndex ;
    private int mBoxCount = 4;
    private int mDrawBoxLineSize = 4;
    private int mInterval;
    private List<String> mPasswordList;
    private PasswordLayout.onPasswordChangeListener mPwdChangeListener;
    private StringBuffer mPassString;

    public PasswordLayout(Context context) {
        this(context, null);
    }

    public PasswordLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPassword(context, attrs);
    }

    private void initPassword(Context context, AttributeSet attrs) {
        mContext = context;
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PassWordLayoutStyle);
        mRemindLineColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_input_line_color, R.color.color_white);
        mInputTextColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_text_input_color, Color.parseColor("#3DA3FF"));
        mBoxDrawType = ta.getInt(R.styleable.PassWordLayoutStyle_box_draw_type, 0);
        mShowPassType = ta.getInt(R.styleable.PassWordLayoutStyle_pass_inputed_type, 0);
        mBoxWidth = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_width, 40);
        mBoxHeight = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_height, 40);
        mDrawTxtSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_txt_size, 18);
        mDrawBoxLineSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_box_line_size, 4);
        mBoxCount = ta.getInt(R.styleable.PassWordLayoutStyle_passwordBox_count, 4);
        mInterval = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_interval_width, 5);
        mPasswordList = new ArrayList<>();
        //由于内部是单例线程池,所以需要进行回收,否则浪费大量资源
        ta.recycle();
        //按下的时候弹出软键盘
        setOnClickListener(mOnClickListener);
        //设置password获取焦点监听
        setOnFocusChangeListener(mOnFocusChangeListener);
        //读取键盘输入的密码
        setOnKeyListener(mOnKeyListener);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildCount() == 0) {     //判断 子View宽+边距是否超过了父布局 超过了则重置宽高
            if ((mBoxWidth * mBoxCount + (mBoxCount - 1) * mInterval) > getMeasuredWidth()) {
                mBoxWidth = (getMeasuredWidth() - (mBoxCount - 1) * mInterval) / mBoxCount;
                mBoxHeight = mBoxWidth;
            }
            addChildViews(getContext());
        }
    }

    /**
     * 添加子View
     *
     * @param context
     */
    private void addChildViews(Context context) {
        for (int i = 0; i < mBoxCount; i++) {
            PasswordView passWordView = new PasswordView(context);
            LayoutParams params = new LayoutParams(mBoxWidth, mBoxHeight);
            if (i > 0) {                                       //第一个和最后一个子View不添加边距
                params.leftMargin = mInterval;
                passWordView.setBoxHeight(mBoxHeight);
                passWordView.setBoxWidth(mBoxWidth);
                passWordView.setBoxStrokeWidth(10);
                passWordView.setBoxType(0);
                passWordView.setTextSize(10);
                passWordView.setInputBoxBgColor(Color.parseColor("#4475AE"));
                passWordView.setPwdEncryptionColor(Color.parseColor("#c8c8c8"));
                passWordView.setPwdEncryptionType(0);
                passWordView.setShowInputCursor(true);
            }
            addView(passWordView, params);
        }
    }

    private OnClickListener mOnClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            setFocusable(true);
            setFocusableInTouchMode(true);
            requestFocus();
            InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.showSoftInput(PasswordLayout.this, InputMethodManager.SHOW_IMPLICIT);
        }
    };

    private OnFocusChangeListener mOnFocusChangeListener = new OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            Logger.d(Logger._JN, "onFocusChange mCurIndex :%s ", mCurIndex);
            if (hasFocus) {
                if (mCurIndex == 4) {
                    removePwd();
                }else {
                    drawFlashStatus(mCurIndex);
                }
            } else {
                if (mCurIndex == 0){
                    clearInputStatus(mCurIndex);
                }
            }
        }
    };

    private OnKeyListener mOnKeyListener = new OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
                    addPwd(keyCode - 7 + " ");
                    return true;
                }
                if (keyCode == KeyEvent.KEYCODE_DEL) {
                    removePwd();
                    return true;
                }
            }
            return false;
        }
    };

    private void addPwd(String pwd) {
        if (mPasswordList != null && mPasswordList.size() < mBoxCount) {
            mPasswordList.add(pwd);
            drawInputStatus(mCurIndex);
            if (mCurIndex < mBoxCount ) {
                drawFlashStatus(++mCurIndex);
            }
        }

        if (mPasswordList.size() < mBoxCount) {
            mPwdChangeListener.onChange(getPassString());
        } else {
            mPwdChangeListener.onFinished(getPassString());
        }
    }

    private void removePwd() {
        if (mPasswordList.size() == 0){
            mPwdChangeListener.onNull();
        }
        
        if (mPasswordList != null && mPasswordList.size() > 0) {
            mPasswordList.remove(mPasswordList.size() - 1);
            clearInputStatus(mCurIndex);
            if (mCurIndex >= 1) {
                drawFlashStatus(--mCurIndex);
            }
        }
        if (mPasswordList.size() != 0) {
            mPwdChangeListener.onChange(getPassString());
        }


    }

    public String getPassString() {
        mPassString = new StringBuffer();
        for (String i : mPasswordList) {
            mPassString.append(i.trim());
        }
        Logger.d(Logger._JN, "getPassString  :%s  ", mPassString.toString());
        return mPassString.toString();
    }

    /**
     * 三种状态:清除所有状态 显示密码 显示闪烁
     * 清除操作:当前清除 前一个显示闪烁
     * 添加操作:当前显示密码 后一个显示闪烁
     * 注意第一个和最后一个的边界
     *
     * @param index
     */
    private void drawInputStatus(int index) {
        Logger.d(Logger._JN, "drawInputStatus  :%s ", index);
        PasswordView passwordView = (PasswordView) getChildAt(index);
        passwordView.drawInputPwd();
    }

    public void drawFlashStatus(int index) {
        if (index >= 4) {
            return;
        }
        Logger.d(Logger._JN, "drawFlashStatus  :%s ", index);
        PasswordView passwordView = (PasswordView) getChildAt(index);
        if (passwordView != null) {
            passwordView.showInputStatus();
        }
    }

    public void clearInputStatus(int index) {
        if (index >= 4) {
            return;
        }
        Logger.d(Logger._JN, "clearInputStatus  :%s ", index);
        PasswordView passwordView = (PasswordView) getChildAt(index);
        passwordView.clearAllPwd();
    }

    public void setOnPasswordChangeListener(PasswordLayout.onPasswordChangeListener listener) {
        mPwdChangeListener = listener;
    }

    public interface onPasswordChangeListener {
        void onChange(String pwd);

        void onFinished(String pwd);

        void onNull();
    }

}
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <!--密码输入layout-->
    <declare-styleable name="PassWordLayoutStyle">
        <attr name="box_input_color" format="reference"></attr>//输入框输入状态颜色
        <attr name="box_no_input_color" format="reference"></attr>//输入框未输入状态颜色
        <attr name="input_line_color" format="reference"></attr>//输入线颜色
        <attr name="text_input_color" format="reference"></attr>//文本颜色
        <attr name="interval_width" format="dimension"></attr>//间隔
        <attr name="item_width" format="dimension"></attr>//子View宽
        <attr name="item_height" format="dimension"></attr>//子View高
        <attr name="draw_txt_size" format="dimension"></attr>//文本大小
        <attr name="draw_box_line_size" format="dimension"></attr>//输入线条大小
        <attr name="is_show_input_line" format="boolean"></attr>//是否显示输入线
        <attr name="pass_inputed_type">//密码输入显示内容
            <flag name="circle" value="0" />
            <flag name="stars" value="1" />
            <flag name="text" value="2" />
        </attr>
        <attr name="box_draw_type">//密码框形状
            <flag name="rect" value="0" />
            <flag name="circle" value="1" />
            <flag name="line" value="2" />
        </attr>
        <attr name="pass_length">//密码长度 只考虑4 6 8
            <flag name="four" value="4" />
            <flag name="six" value="6" />
            <flag name="eight" value="8" />
        </attr>
    </declare-styleable>
</resources>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值