天天看點

Android自定義密碼輸入框Android自定義密碼輸入框

Android自定義密碼輸入框

項目位址

項目需要用到密碼框輸入,并且使用自定義的鍵盤,但是密碼框需要區分輸入完成、待輸入、未輸入顔色百度一番沒有結果就自己自定義一個了

自定義鍵盤 (我做的比較簡單就不累贅了)

我這裡使用的是簡單的鍵盤 需要看源碼點選這裡

自定義輸入框View

1.控件屬性定義,自己根據需求羅列一下感覺需要的屬性如下

屬性名稱 作用
textColor 文字顔色預設黑色
textSize 文文字尺寸預設 22
count 輸入框個數預設6
width 輸入框寬度預設40dp
height 輸入框高度預設40dp
lineColor 預設狀态的邊框顔色 預設黑色
fillColor 預設狀态的填充顔色 預設白色
lineWidth 預設狀态的邊框寬度 預設1dp
focusLColor 預設狀态的邊框顔色 預設黑色
focusFillColor 預設狀态的填充顔色 預設白色
focusLineWidth 預設狀态的邊框寬度 預設1dp
employLColor 預設狀态的邊框顔色 預設黑色
employFillColor 預設狀态的填充顔色 預設白色
employLineWidth 預設狀态的邊框寬度 預設1dp
isContinuous 輸入框是否連續(友善以後其他需要就添加了)預設true 連續
borderRadius 文輸入框邊角半徑 預設0dp
conceal 是否隐藏文字 預設false 不隐藏
replaceString 文字隐藏替換字元 預設沒有
replaceDrawable 文字隐藏替換圖檔(優先級高于replaceString) 預設沒有
circleRadius 預設替換圖案半徑(圓形) 預設為width的三分之一
circleColor 預設替換圖案顔色 預設與textColor 一緻
isContinuousRepeatChar 是否過濾連續重複的字元 預設false 不過濾
isContinuousChar 是否過濾連續的字元 預設false 不過濾
isInvokingKeyboard 是否使用系統鍵盤 預設為true 如果為false的換需要自己手動調起鍵盤

根據自己的需求并考慮到我們後期的擴充性定義了如上屬性,屬性定義這個也比較簡單就不過多的描述:

2.初始化

首先我們要擷取屬性

通過:TypedArray typedArray =

context.obtainStyledAttributes(attrs, R.styleable.BorderPWEditText);擷取到typedArray 對象,使用typedArray對象我們将會擷取到使用者設定的屬性并可以指定屬性預設值

初始化畫筆

private void initPaint() {

//文字畫筆

paintText = new Paint(Paint.ANTI_ALIAS_FLAG);

paintText.setTextAlign(Paint.Align.CENTER);

paintText.setAntiAlias(true);

paintText.setTextSize(mTextSize);

paintText.setColor(mTextColor);

//邊框畫筆
    borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    borderPaint.setStrokeWidth(mLineWidth);
    borderPaint.setColor(mLineColor);
    borderPaint.setAntiAlias(true);
    borderPaint.setStyle(Paint.Style.STROKE);

    //填充畫筆
    fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    fillPaint.setColor(mFillColor);
    fillPaint.setAntiAlias(true);
    fillPaint.setStyle(Paint.Style.FILL);

}
           

目前定義了三隻畫筆,可能有人說你不是有預設邊框填充,待輸入邊框填充,未輸入邊框填充類型嗎?但是我們梳理一下你你會發現 預設、待輸入、未輸入情況下畫筆的顔色和寬度不同,起始就隻有填充和邊框畫筆類型是以我們就少定義一些變量,我們方法修改畫筆屬性并傳回如下:我們隻用傳目前的狀态

private Paint getBorderPaint(InputStatus status) {
        if (InputStatus.No_Input == status) {
            borderPaint.setStrokeWidth(mLineWidth);
            borderPaint.setColor(mLineColor);
        } else if (InputStatus.To_Input == status) {
            borderPaint.setStrokeWidth(mFocusLineWidth);
            borderPaint.setColor(mFocusLineColor);
        } else if (InputStatus.Have_Input == status) {
            borderPaint.setStrokeWidth(mEmployLineWidth);
            borderPaint.setColor(mEmployLineColor);
        }
        return borderPaint;
    }

    private Paint getFillPaint(InputStatus status) {
        if (InputStatus.No_Input == status) {
            fillPaint.setColor(mFillColor);
        } else if (InputStatus.To_Input == status) {
            fillPaint.setColor(mFocusFillColor);
        } else if (InputStatus.Have_Input == status) {
            fillPaint.setColor(mEmployFillColor);
        }

        return fillPaint;
    }
           

我們話預設替換圖案的畫筆呢,我們這個畫筆不一定适用,因為使用者可能定義替換文字我們就直接适用文字畫筆了,如果使用者使用替換圖案我們話圖适不使用畫筆的,隻有繪制預設替換圖适需要,是以我們這個畫筆在需要時再去延遲建立

根據屬性值初始化部配置設定置

還有什麼屬性需要設定呢?我當時也是不請求根據自己控件需要動态添加的,但是如果我們繼承EditText的話就需要注意了在初始完需要調用setBackgroundColor(Color.TRANSPARENT);進行處理,如果不處理你就能看到你繪制的控件上覆寫着一條線,通過Xml布局設定 background會引發其問題具體原因還沒有深究。可能我們還需要使用setFilters屬性設定部分攔截比如長度,重複處理等。我們在做其他自定義控件是就可以在這裡處理一個預設配置

開始測量,我們沒有指定測量的方式,這個根據自己定義控件的特性自行修改onMeasure完成測量

我們需要指定控件的繪制起始位置我們需要重新onSizeChanged方法這個方法在控件尺寸改變是會調用我們設定如下:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (isContinuous) {
            startX = (w - count * mWidth) / 2;
        } else {
            startX = (w - count * mWidth - (count - 1) * intervalWidth) / 2;
        }
    }
           

繪制:接下來就是繪制的我們需要重新onDraw方法,看一下我們這裡很清晰因為我把操作方法都封裝了

@Override
    protected void onDraw(Canvas canvas) {
        //繪制邊框
        drawPeripheryBorder(canvas, position);
        if (isConceal) {
            //繪制替換符
            drawConceal(canvas);
        } else {
            //繪制文字
            drawText(canvas);
        }
    }
           

看看我們的具體實作:

先看drawPeripheryBorder()

/**
     * 背景框繪制
     *
     * @param canvas
     * @param position
     */
    private void drawPeripheryBorder(Canvas canvas, int position) {

		循環所有的邊框
        for (int i = 0; i < count; i++) {
        計算目前位置邊框的左上角與右下角的坐标點
            float left = startX + i * mWidth;
            float top = 0;
            float right = left + mWidth;
            float bottom = mHeight;
            if (!isContinuous) {
                left = left + i * intervalWidth;
                right = left + mWidth;
            }

            if (i < position) {
			//輸入過的位置
                if (i == 0) {
                    PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.Have_Input);
                } else if (i == count - 1) {
                    PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.Have_Input);
                } else {
                    PeripheryBorder(canvas, left, top, right, bottom, 3, InputStatus.Have_Input);
                }

            } else if (i > position) {
			//未輸入的邊框
                if (i == 0) {
                    PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.No_Input);
                } else if (i == count - 1) {
                    PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.No_Input);
                } else {
                    PeripheryBorder(canvas, left, top, right, bottom, 3, InputStatus.No_Input);
                }


            }
        }

        //結束時繪制選中框避免别覆寫
        if (position < count) {

            float left = startX + position * mWidth;
            float top = 0;
            float right = left + mWidth;
            float bottom = mHeight;
            if (!isContinuous) {
                left = left + position * intervalWidth;
                right = left + mWidth;
            }

            if (position == 0) {
                PeripheryBorder(canvas, left, top, right, bottom, 1, InputStatus.To_Input);
            } else if (position == count - 1) {
                PeripheryBorder(canvas, left, top, right, bottom, 2, InputStatus.To_Input);
            } else {
                PeripheryBorder(canvas, left - mFocusLineWidth / 2, top, right + mFocusLineWidth / 2, bottom, 3, InputStatus.To_Input);
            }
        }
    }

看到嗎這裡嗎處理不同繪制的流程具體繪制還有方法
 /**
     * 繪制背景
     *
     * @param canvas       畫布
     * @param left         左邊坐标
     * @param top          頭坐标
     * @param right        右坐标
     * @param bottom       下坐标
     * @param locationType 1.左邊,2.右邊 3.中間
     * @param status       表示目前位置
     */
    public void PeripheryBorder(Canvas canvas, float left, float top, float right, float bottom, int locationType, InputStatus status) {

        int lineWidth = status == InputStatus.No_Input ? mLineWidth : (status == InputStatus.Have_Input ? mEmployLineWidth : mFocusLineWidth);
        if (locationType == 1) {
//繪制最左邊(因為我們有圓角,因為連續的時候隻有最左邊與最右邊有圓角)
            Path FillPathRoundRect = new Path();
            FillPathRoundRect.addRoundRect(
                    new RectF(left, top + lineWidth, right, bottom - lineWidth),
                    (isContinuous ? getLeftRadius() : getAllRadius()), //看到這句了嗎,他是判斷目前邊框是繪制的圓角位置
                    Path.Direction.CCW
            );
            canvas.drawPath(FillPathRoundRect, getFillPaint(status));

            Path borderPathRoundRect = new Path();
            borderPathRoundRect.addRoundRect(
                    new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
                    isContinuous ? getLeftRadius() : getAllRadius(),
                    Path.Direction.CCW
            );
            canvas.drawPath(borderPathRoundRect, getBorderPaint(status));


        } else if (locationType == 2) {
//繪制最右邊邊框
            Path FillPathRoundRect = new Path();
            FillPathRoundRect.addRoundRect(
                    new RectF(left, top + lineWidth, right, bottom - lineWidth),
                    isContinuous ? getRightRadius() : getAllRadius(),//看到這句了嗎,他是判斷目前邊框是繪制的圓角位置
                    Path.Direction.CCW
            );
            canvas.drawPath(FillPathRoundRect, getFillPaint(status));

            Path borderPathRoundRect = new Path();
            borderPathRoundRect.addRoundRect(
                    new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
                    isContinuous ? getRightRadius() : getAllRadius(),
                    Path.Direction.CCW
            );
            canvas.drawPath(borderPathRoundRect, getBorderPaint(status));


        } else if (locationType == 3) {

//繪制中間邊框
            if (isContinuous) {  
                canvas.drawRect(
                        new RectF(left, top + lineWidth, right, bottom - lineWidth), getFillPaint(status)
                );
                canvas.drawRect(new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2), getBorderPaint(status));

            } else {

                Path FillPathRoundRect = new Path();
                FillPathRoundRect.addRoundRect(
                        new RectF(left, top + lineWidth, right, bottom - lineWidth),
                        getAllRadius(),
                        Path.Direction.CCW
                );
                canvas.drawPath(FillPathRoundRect, getFillPaint(status));

                Path borderPathRoundRect = new Path();
                borderPathRoundRect.addRoundRect(
                        new RectF(left, top + lineWidth / 2, right, bottom - lineWidth / 2),
                        getAllRadius(),
                        Path.Direction.CCW
                );
                canvas.drawPath(borderPathRoundRect, getBorderPaint(status));
            }
        }
    }
           

接着我們看drawText()文字繪制方法:簡單的文本繪制

/**
     * 繪制文字
     */
    private void drawText(Canvas canvas) {
        char[] chars = getText().toString().toCharArray();

        for (int i = 0, n = chars.length; i < n; i++) {
            //繪制輸入狀态
            Paint.FontMetrics fontMetrics = paintText.getFontMetrics();
            int baseLineY = (int) (mWidth / 2 - fontMetrics.top / 2 - fontMetrics.bottom / 2);
            canvas.drawText(
                    String.valueOf(chars[i]),
                    (startX + i * mWidth + mWidth / 2 + (isContinuous ? 0 : i * intervalWidth)),
                    baseLineY,
                    paintText
            );

        }

    }
           

drawConceal(canvas)方法:這個方法也很簡單就是判斷邏輯具體的操作有是方法,具體代碼我就不貼了需要看源碼點選這裡

/**
     * 繪制隐藏圖示
     *
     * @param canvas
     */
    public void drawConceal(Canvas canvas) {

        if (mReplaceDrawable != null) {
            drawDrawableConceal(canvas); //繪制使用者指定的遮蓋圖示
        } else if (!TextUtils.isEmpty(mReplaceString)) {
            drawReplaceText(canvas); //繪制使用者設定的遮蓋字元
        } else {
            drawCircle(canvas); //設定預設的遮蓋圖案
        }

    }
           

到此我們的自定控件的代碼基本完成,起始自定義控件難點就在測量繪制這裡,當我們解決了測量繪制的問題基本上自定義view就大功告成了,後續可能需要添加部分設定啊,回調啊進行更友好的互動比如我們這個控件就添加了setmInputOverListener屬性用于監聽使用者輸入完成回調

自定義密碼輸入框的使用

添加依賴

1.在項目根build.gradle中添加如下代碼

allprojects {

repositories {

maven { url “https://jitpack.io” }

}

}

2. 在使用的Module中添加如下引用

dependencies {

implementation ‘com.github.rupertoL:SpecialView:1.2’

}

3.布局Xml中使用:

<cn.lp.input_library.BorderPWEditText
        android:id="@+id/BorderPWEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:inputType="text"
        app:borderRadius="5dp"
        app:conceal="true"
        app:employFillColor="#FF9800"
        app:employLColor="#F44336"
        app:employLineWidth="2dp"
        app:focusFillColor="#009688"
        app:focusLColor="#673AB7"
        app:focusLineWidth="2dp"
        app:height="40dp"
        app:isContinuousChar="true"
        app:lineWidth="2dp"
        app:width="40dp"
        />
           

根據前面的屬性說明自行添加相應屬性實作效果

4.監聽輸入

BorderPWEditText.setmInputOverListener(object : BorderPWEditText.InputOverListener {
            override fun InputOver(string: String?) {
                Toast.makeText(baseContext, "目前接收的資料為:${string}", Toast.LENGTH_LONG).show()
            }

            override fun InputHint(string: String?) {
                Toast.makeText(baseContext, string, Toast.LENGTH_LONG).show()
            }

        })
           

當然我們還可以使用最簡單的方式,就是什麼也不配置

<cn.lp.input_library.BorderPWEditText
        android:id="@+id/PWEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:inputType="number"
     />
           
Android自定義密碼輸入框Android自定義密碼輸入框

使用就ok了,簡單吧!希望對您有幫助,後期持續更新其他元件

支援多種布局Banner元件