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"
     />

在這裏插入圖片描述

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

支持多種佈局Banner組件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章