自定义密码 输入界面

知识点:selector,自定义View,RecyclerView

最终界面如下,按下一个数字按钮,则上面就会显示中间一个小灰点:

思路:

  1. 写一个自定义View,然后背景色是灰色,用一个画笔画的.然后前景色是更灰一点儿的颜色,用画笔画的.然后前景色可以设置为没有,这样看起来就是只有背景色了.
  2. 然后下面的是RecyclerView,点一下一个按钮,上面的最后自定义View就显示出中间的小灰点,就像是输入了一个密码一样.
  3. 当密码位数等于4的时候,判断是否输入正确.

1. 首先需要一个自定义View,用来显示密码的输入框的.CirclePsdView.java

public class CirclePsdView extends View {

    private static final String TAG = "CirclePsdView";
    private Paint mBGPaint;  //背景画笔
    private Paint mFGPaint;  //前景画笔
    private int mRadius;   //View的半径
    private static final int SMALL_CIRCLE_RADIUS = 10;  //内圆半径

    public CirclePsdView(Context context) {
        super(context);
        init();
    }

    public CirclePsdView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CirclePsdView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化设置
     */
    private void init() {
        mBGPaint = new Paint();
        mFGPaint = new Paint();
        mBGPaint.setARGB(255, 239, 239, 239);//设置背景画笔颜色
        mFGPaint.setColor(Color.TRANSPARENT); //设置前景画笔颜色   默认是没有密码,是透明的
    }

    //测量自己的宽高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //因为是圆形,所以应该让宽高保持一致
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);

        //该方法必须由onMeasure(int,int)调用来存储测量的宽度和测量高度。 如果没有这样做,将在测量时间触发异常。
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        //1, 获取当前控件的宽高,并设置半径的大小
        int w = this.getMeasuredWidth();
        int h = this.getMeasuredHeight();
        mRadius = w < h ? w / 2 : h / 2;

        //2, 首先画背景  #EFEFEF
        canvas.drawCircle(mRadius, mRadius, mRadius, mBGPaint);

        //3, 其次画中间的颜色深一点的圆圈   #B6B6B6
        //画圆形,指定好中心点坐标、半径、画笔
        canvas.drawCircle(mRadius, mRadius, SMALL_CIRCLE_RADIUS, mFGPaint);
    }

    //设置是否存在密码
    public void setHadPass(boolean exist) {
        LogUtil.d(TAG, "密码输入框切换状态");
        //1, 判断是否存在密码    设置前景色
        if (exist) {
            mFGPaint.setARGB(255, 182, 182, 182);//设置画笔颜色
        } else {
            mFGPaint.setColor(Color.TRANSPARENT);   //设置为透明
        }
        //2, 重绘界面
        invalidate();
    }

    /**
     * 测量View的长度
     */
    private int measureHeight(int measureSpec) {
        int result = 0;

        //获取具体的测量模式和大小
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) { //默认的大小(指定了具体的值  比如android:layout_width=100dp)
            result = measureSpec;
        } else {
            result = 45;
            if (specMode == MeasureSpec.AT_MOST) { //wrap_content
                result = Math.min(result, specSize);
            }
        }

        return result;
    }

    /**
     * 测量View的宽度
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);   //获取具体的测量模式(EXACTLY,AT_MOST,UNSPECIFIED)
        int specSize = MeasureSpec.getSize(measureSpec); //获取具体的测量大小

        if (specMode == MeasureSpec.EXACTLY) {   //默认模式
            result = measureSpec;
        } else {
            result = 45;   //给一个默认的大小
            if (specMode == MeasureSpec.AT_MOST) {   //如果是wrap_content
                result = Math.min(result, specSize);   //取最小的  适合控件大小的
            }
        }

        return result;
    }

}

2. 然后需要建Activity的布局文件activity_enter_pas.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xfhy.mobilesafe.activity.EnterPasActivity">

    <!--程序锁拦截界面布局-->

    <!--返回按钮-->
    <ImageButton
        android:id="@+id/ib_back"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:background="@android:color/transparent"
        android:padding="5dp"
        android:src="@drawable/back"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="64dp"
        android:layout_toRightOf="@id/ib_back"
        android:gravity="center_vertical"
        android:text="应用锁"
        android:textColor="@color/colorBlack"
        android:textSize="20sp"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_below="@id/ib_back"
        android:background="@color/colorGray"/>

    <!--下面的数字   用来输入密码的-->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_keyboard"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"/>

    <LinearLayout
        android:id="@+id/ll_pass_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/rv_keyboard"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <!--密码输入框-->
        <com.xfhy.mobilesafe.view.CirclePsdView
            android:id="@+id/cpv_pass1"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp"
            android:layout_marginBottom="64dp"/>

        <!--密码输入框-->
        <com.xfhy.mobilesafe.view.CirclePsdView
            android:id="@+id/cpv_pass2"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp"
            android:layout_marginBottom="64dp"/>

        <!--密码输入框-->
        <com.xfhy.mobilesafe.view.CirclePsdView
            android:id="@+id/cpv_pass3"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp"
            android:layout_marginBottom="64dp"/>

        <!--密码输入框-->
        <com.xfhy.mobilesafe.view.CirclePsdView
            android:id="@+id/cpv_pass4"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp"
            android:layout_marginBottom="64dp"/>

    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/ll_pass_input"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:text="输入密码"
        android:textColor="@color/colorBlack"
        android:textSize="20sp"/>

</RelativeLayout>

3. 再新建一个RecyclerView的子项布局文件layout_pass_keyborad_item.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/bt_keyboard"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/selector_keyborad_bg"
        android:gravity="center"
        android:text="1"
        android:textColor="@color/colorBlack"
        android:textSize="20sp"
        android:textStyle="bold"/>

4. 再新建一个selector,RecyclerView子项的圆形selector,在res/drawable下面新建 selector_keybroad_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!--九宫格界面的子项点击时的selector-->
    <item  android:state_focused="true"
           android:state_enabled="true"
           android:state_pressed="false">
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorGray"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>
    <item android:state_enabled="true"
          android:state_pressed="true" >
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorGray"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>
    <item android:state_enabled="true"
          android:state_checked="true">
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorGray"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>
    <item android:state_focused="false"
          android:state_enabled="true"
          android:state_pressed="true" >
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorGray"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>
    <item android:state_enabled="true" android:state_focused="true"
          android:state_pressed="false">
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorGray"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>
    <item android:color="@color/colorTransparent" >
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid android:color="@color/colorTransparent"/>
            <size
                android:width="80dp"
                android:height="80dp"/>

        </shape>
    </item>

</selector>

为了在API 21以上有更好的体验,需要在res下新建drawable-v21文件夹,再新建selector_keybroad_item.xml

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/colorGray">
    <item android:drawable="@color/colorWhite"/>
</ripple>

5. 最后,界面的Activity,EnterPasActivity.java

public class EnterPasActivity extends BaseActivity {

    private static final String TAG = "EnterPasActivity";
    @BindView(R.id.ib_back)
    ImageButton ibBack;
    @BindView(R.id.rv_keyboard)
    RecyclerView rvKeyboard;
    @BindView(R.id.cpv_pass1)
    CirclePsdView cpvPass1;
    @BindView(R.id.cpv_pass2)
    CirclePsdView cpvPass2;
    @BindView(R.id.cpv_pass3)
    CirclePsdView cpvPass3;
    @BindView(R.id.cpv_pass4)
    CirclePsdView cpvPass4;

    /**
     * 按钮上需要显示的内容
     */
    private String[] mKeyTitleStrs;
    private Context mContext;
    private MyKeyBoardAdapter mAdapter;
    /**
     * 当前已经输入的密码的个数
     */
    private int passInputCount = 0;
    /**
     * 用来存放密码的数组
     */
    private int passArray[] = {-1, -1, -1, -1};
    private static final int WAIT_DRAW = 1000;
    /**
     * 存放密码输入框
     */
    private CirclePsdView[] psdViewArray;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case WAIT_DRAW:

                    //2, 清空数组
                    for (int i = 0; i < passArray.length; i++) {
                        passArray[i] = -1;
                        psdViewArray[i].setHadPass(false);
                    }

                    //3, 设置输入的密码位数为0
                    passInputCount = 0;

                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_enter_pas);
        mContext = this;
        ButterKnife.bind(this);

        initData();
    }

    /**
     * 初始化数据
     */
    private void initData() {
        //1, 准备数据  自定义键盘
        mKeyTitleStrs = new String[]{
                "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "×"
        };
        //2, 创建适配器
        mAdapter = new MyKeyBoardAdapter();


        //设置adapter
        rvKeyboard.setAdapter(mAdapter);
        //设置LayoutManager
        GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, 3);
        rvKeyboard.setLayoutManager(gridLayoutManager);

        //存放密码输入框
        psdViewArray = new CirclePsdView[]{cpvPass1, cpvPass2, cpvPass3, cpvPass4};
    }

    /**
     * 九宫格的适配器RecyclerView
     */
    private class MyKeyBoardAdapter extends RecyclerView.Adapter<MyKeyBoardAdapter
            .ViewHolder> {

        class ViewHolder extends RecyclerView.ViewHolder {
            TextView btKeyboard;

            public ViewHolder(View itemView) {
                super(itemView);

                btKeyboard = (TextView) itemView.findViewById(R.id.bt_keyboard);
            }
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(mContext).inflate(R.layout.layout_pass_keyboard_item,
                    parent, false);
            return new ViewHolder(view);
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, final int position) {
            //1, 设置按钮上的内容
            if (mKeyTitleStrs != null && position < mKeyTitleStrs.length) {
                holder.btKeyboard.setText(mKeyTitleStrs[position]);
            }

            //2, 设置第10个按钮不可用
            if (position == 9) {
                holder.btKeyboard.setVisibility(View.GONE);
            }

            //3, 设置按钮点击事件
            holder.btKeyboard.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    LogUtil.d(TAG, "position--------" + position);


                    //输入的密码个数不对   下标越界
                    if (passInputCount < 0 || passInputCount > 3) {
                        return;
                    }

                    //点击了一下键盘,则密码位数+1
                    passInputCount++;

                    //判断到底点了哪个按钮
                    switch (position) {
                        case 0:
                            passArray[passInputCount - 1] = 1;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 1:
                            passArray[passInputCount - 1] = 2;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 2:
                            passArray[passInputCount - 1] = 3;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 3:
                            passArray[passInputCount - 1] = 4;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 4:
                            passArray[passInputCount - 1] = 5;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 5:
                            passArray[passInputCount - 1] = 6;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 6:
                            passArray[passInputCount - 1] = 7;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 7:
                            passArray[passInputCount - 1] = 8;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 8:
                            passArray[passInputCount - 1] = 9;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 10:
                            passArray[passInputCount - 1] = 0;
                            psdViewArray[passInputCount - 1].setHadPass(true);
                            break;
                        case 11:
                            LogUtil.d(TAG, "干掉: passInputCount" + passInputCount);
                            if (passInputCount == 1) {
                                passInputCount = 0;
                                return;
                            }
                            passArray[passInputCount - 2] = -1;
                            psdViewArray[passInputCount - 2].setHadPass(false);
                            passInputCount -= 2;

                            break;
                    }



                    //输入的密码位数达到4位
                    if (passInputCount == 4) {
                        //1, 判断密码是否正确
                        if (passArray[0] == 1 && passArray[1] == 1 && passArray[2] == 1 &&
                                passArray[3] == 1) {
                            ToastUtil.showSuccess("密码验证成功");
                        } else {
                            ToastUtil.showError("密码验证失败");
                        }

                        //开一个子线程,等待500毫秒再更新UI,不然最后一个控件还没来得及画出来,就被设置为false了
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(500);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }

                                //发送 等待完成 信号   可以刷新UI啦
                                mHandler.sendEmptyMessage(WAIT_DRAW);
                            }
                        }).start();

                    }

                }
            });
        }

        @Override
        public int getItemCount() {
            return mKeyTitleStrs == null ? 0 : mKeyTitleStrs.length;
        }
    }

}
posted @ 2017-05-22 23:00  潇风寒月  阅读(23)  评论(0)    收藏  举报