1. 程式人生 > >自定義view---強大的密碼輸入框

自定義view---強大的密碼輸入框

我司之前有個需求,要求類似支付寶那樣的密碼支付,產品要求輸入的當前字元需要是明文密碼,1s後轉換為圓點,原本想網上那麼多密碼輸入框,肯定沒問題,結果UI一出圖就懵逼了,翻遍了各個角落,都找不到類似的密碼輸入框,沒辦法,自己寫吧。
使用方法,在gradle中新增:compile ‘com.rokudoll:PswText:1.0.0’即可使用
當然繪製思路參照了其他大佬的思路,言歸正傳,先來看看效果圖:

需求分析

拿到效果圖,再結合產品的要求,整理出以下要求:
1、類似EditText的password模式,輸入密碼時,輸入的當前密碼為明文,而之前的密碼變為圓點,1s後當前密碼也變為圓點
2、整體密碼框為一個顏色,而已輸入或當前輸入密碼位置的密碼框為另一個顏色
3、有陰影

程式碼實現

自定義view的流程就不再贅述了,不太清楚的可以去看看鴻洋和郭林的教程,先寫好自定義屬性,按照之前分析的結果以及自己的一點想法,為了更方便的使用,就有了以下的自定義屬性,目前沒有使用列舉,全部都是以boolean值來規定使用哪種模式,不過之後會換成列舉,那麼先來看看有哪些自定義屬性:

attrs

屬性名 作用
pswLength integer 規定密碼長度,預設為6
delayTime integer 延遲繪製密碼圓點的時間 預設1000,1000=1s
borderColor color 初始化密碼框顏色
pswColor color 密碼顏色
inputBorder_color color 輸入時密碼框顏色
borderShadow_color color 輸入時密碼框陰影顏色
psw_textSize sp 明文密碼大小
borderRadius dp 不使用圖片時,密碼框圓角大小
borderImg drawable 密碼框圖片
inputBorderImg drawable 輸入時變化的密碼框圖片
isDrawBorderImg boolean 是否使用圖片繪製密碼框,為true時設定borderImg、inputBorderImg才有效,預設為false
isShowTextPsw boolean 按下back鍵時是否需要繪製當前位置的明文密碼,預設為false
isShowBorderShadow boolean 輸入密碼時是否需要繪製陰影,為true時設定borderShadow_color才有效,預設為false
clearTextPsw boolean 是否只繪製明文密碼,預設為false
darkPsw boolean 是否只繪製圓點,預設為false
isChangeBorder boolean 是否在輸入密碼時不更改密碼框顏色,預設為false

設定好自定義屬性後,就開始實現這個自定義控制元件了!首先肯定是計算寬高

onMeasure

作為一個數學很差的人,計算這個的過程確實是比較糟心的。。直接看圖吧

圖中說明兩處:
1、spacingWidth:為什麼在寬度已知的情況下,spacingWidth = borderWidth / 4:
很簡單,因為用邊框的寬度除以4得到的大小剛好是能讓我接受的大小,且在pswLength = 7、8、9、10的時候,寬度也比較合適,說白了就是一點點湊出來的
2、borderWidth:為什麼在寬度已知的情況下,borderWidth = (width 4) / ((5 pswLength) - 1),
這個其實很好理解,當寬度已知的時候,borderWidth = width / 6 - spacingWidth,即寬度除以6減去一個間隙的寬度就等於一個邊框的寬度,而間隙的寬度 = borderWidth /4,那麼我們換算成一個方程式來看看,就一目瞭然了,如果這個方程式還看不懂,那就去請教一下初中數學老師吧。。

來看看完整程式碼:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpec = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpec == MeasureSpec.AT_MOST) {
            if (heightSpec != MeasureSpec.AT_MOST) {//高度已知但寬度未知時
                spacingWidth = heightSize / 4;
                widthSize = (heightSize * pswLength) + (spacingWidth * (pswLength - 1));
                borderWidth = heightSize;
            } else {//寬度,高度都未知時
                widthSize = (borderWidth * pswLength) + (spacingWidth * (pswLength - 1));
                heightSize = (int) (borderWidth + ((borderPaint.getStrokeWidth()) * 2));
            }
        } else {
            //寬度已知但高度未知
            if (heightSpec == MeasureSpec.AT_MOST) {
                borderWidth = (widthSize * 4) / (5 * pswLength);
                spacingWidth = borderWidth / 4;
                heightSize = (int) (borderWidth + ((borderPaint.getStrokeWidth()) * 2));
            }
        }
        setMeasuredDimension(widthSize, heightSize);
    }

解釋一下為什麼高度要加上borderPaint.getStrokeWidth()2,很簡單,如果直接取每個密碼框的寬度作為高度的話,會出現密碼框上下兩根線繪製不完全的問題,因為畫筆的寬度也是佔了一定的大小的,所以我們在繪製高度時要留足夠的高度來繪製整個view,為什麼是2呢?因為上下兩根線。。當然要加兩次,所以是乘以2

按這種演算法繪製出的view,其實右邊還多了一個spacingWidth的寬度,因為我們給每一個邊框都減去了一個spacingWidth的寬度,所以會多出一個spacingWidth的寬度,不過並不影響,我們可以在onDraw裡把每一個密碼框往右移0.5個spacingWidth的寬度,這樣左右都空出了一小段空隙,視覺效果上也好看一點,直接來看看onDraw

onDraw

繪製pswLength數量個密碼框

先說說思路,我們用for迴圈的方式,迴圈繪製出pswLength個密碼框,用canvas.drawRoundRect繪製密碼框,如果使用圖片的話,就繪製圖片,那麼來看看canvas.drawRoundRect需要傳遞的引數:

drawRoundRect(RectF rect, float rx, float ry, Paint paint)

rect:RectF物件,邊框的具體座標
rx,ry:圓角x,y軸的半徑
paint:畫筆
那我們先來計算邊框的具體座標,來看看草稿:

沒錯,這草稿就是這麼low,數學差。。就是這麼心酸,不過已經可以看出規律,前面說過,我們是用for迴圈的方式迴圈繪製出各個密碼框,那麼總結一下:
top = 0 + (borderPaint.getStrokeWidth())
bottom = height - (borderPaint.getStrokeWidth())
left = i (borderWidth + spacingWidth)
right = borderWidth + i 
(borderWidth + spacingWidth)
= ((i + 1) borderWidth) + (i spacingWidth)

為什麼top加上了borderPaint.getStrokeWidth(),而bottom又減去了borderPaint.getStrokeWidth()?

還記得我們在onMeasure處計算高度時,增加了borderPaint.getStrokeWidth()*2嗎,我們的密碼框在繪製時,為了把整個view往下移一個畫筆寬度的位置,就需要在top出加上一個畫筆寬度的大小。
而為什麼bottom又要減去一個畫筆寬度呢?仔細想想看,我們的bottom是用view的高度-一個畫筆的寬度,而height是在邊框的寬度基礎上加上了兩個畫筆的寬度的,減去一個就正好是把整個view往下移了一個畫筆的寬度

但是這樣繪製出來的密碼框,就像前面提過的,右邊會多出一個spacingWidth,所以我們left和right的座標需要再改進一下:
int left = (int) ( (i (borderWidth + spacingWidth)) + (0.5 spacingWidth));
int right = (int) (((i + 1) borderWidth) + (i spacingWidth) + (0.7 * spacingWidth));
有同學要問了,為毛right要乘以0.7而不是0.5,因為乘以0.5邊框位置還是不對呀!微調一下,乘以0.7剛剛好。
到這裡並沒有結束,為啥?因為說好的可以用圖片來繪製邊框,我們還沒有實現這一步,不過也很簡單,不需要重新計算座標,直接貼該部分的完整程式碼:

private void drawBorder(Canvas canvas, int height) {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), borderImg);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        for (int i = 0; i < pswLength; i++) {
            int left = (int) ( (i * (borderWidth + spacingWidth)) + (0.5 * spacingWidth));
            int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.7 * spacingWidth));
            if (isBorderImg) {
                Rect dst = new Rect(left, 0, right, height);
                canvas.drawBitmap(bitmap, src, dst, borderPaint);
            } else {
                borderRectF.set(left, 0, right, height);
                canvas.drawRoundRect(borderRectF, borderRadius, borderRadius, borderPaint);
            }
        }
        bitmap.recycle();

    }

只繪製明文密碼模式

這個很簡單,就不多做解釋了

private void drawText(Canvas canvas, String num, int i) {
    Rect mTextBound = new Rect();
    pswTextPaint.getTextBounds(num, 0, num.length(), mTextBound);
    Paint.FontMetrics fontMetrics = pswTextPaint.getFontMetrics();
    float textX = (float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2 - mTextBound.width() / 2) + (0.5 * spacingWidth));
    float textY = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
    if (saveResult != 0 || saveResult < result.size()) {
        canvas.drawText(num, textX, textY, pswTextPaint);
    }
}

說明一點,為什麼在textX最後要加0.5*spacingWidth,因為我們繪製密碼框的時候就多增加了這麼多的寬度,所以繪製文字也要做同樣的操作,同理,繪製圓點時也是一樣,後面不多做贅述

只繪製圓點模式

for (int i = 0; i < result.size(); i++) {
                float circleX = (float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2)  + (0.6 * spacingWidth));
                float circleY = borderWidth / 2;
                int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
                int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.7 * spacingWidth));
                drawBitmapOrBorder(canvas, left, right, height);
                canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
            }

繪製輸入密碼時密碼框變換樣式:

private void drawBitmapOrBorder(Canvas canvas, int left, int right, int height) {
    if (isBorderImg) {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), inputBorderImg);
        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        Rect dst = new Rect(left, 0, right, height);
        canvas.drawBitmap(bitmap, src, dst, inputBorderPaint);
        bitmap.recycle();
    } else {
        borderRectF.set(left, 0, right, height);
        canvas.drawRoundRect(borderRectF, borderRadius, borderRadius, inputBorderPaint);
    }
}

輸入密碼時,輸入的當前密碼為明文,1s後變為圓點模式,即預設模式

if (invalidated) {
                drawDelayCircle(canvas, height, dotRadius);
                return;
            }
            for (int i = 0; i < result.size(); i++) {
                //密碼明文
                String num = result.get(i) + "";
                //圓點座標
                float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.6 * spacingWidth));
                float circleY = borderWidth / 2;
                //密碼框座標
                int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
                int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.7 * spacingWidth));

            drawBitmapOrBorder(canvas, left, right, height);

            drawText(canvas, num, i);

            //當輸入位置 = 輸入的長度時,即判斷當前繪製的位置是否為當前密碼位置,若是則延遲1s後繪製圓點
            if (i + 1 == result.size()) {
                handler.sendEmptyMessageDelayed(1, delayTime);
            }
            //若按下back鍵儲存的密碼 > 輸入的密碼長度,則只繪製圓點
            //即按下back鍵時,不繪製明文密碼
            if (!isShowTextPsw) {
                if (saveResult > result.size()) {
                    canvas.drawCircle((float) ((i * (borderWidth + spacingWidth)) + (borderWidth / 2 + (0.6 * spacingWidth))), circleY, dotRadius, pswDotPaint);
                }
            }
            //當輸入第二個密碼時,才開始繪製圓點
            if (i >= 1) {
                canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
            }
        }
    }

可能有同學注意到,這裡我們計算的圓點座標跟之前計算的不一樣,為什麼呢?因為在這裡,我們計算的圓點座標分為兩種情況:
1、輸入密碼時,已輸入的密碼變為圓點,那麼我們的圓點座標就應該是用(i - 1)的方式去計算,這樣才能實現輸入到第二個密碼時,第一個密碼變為圓點
2、輸入密碼時,延遲1s後當前明文密碼變為圓點,那麼圓點座標就應該是(i + 1)的方式計算

延遲繪製圓點

private void drawDelayCircle(Canvas canvas, int height, int dotRadius) {
    invalidated = false;
    for (int i = 0; i < result.size(); i++) {
        float circleX = (float) (((i - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2) + (0.6 * spacingWidth));
        float circleY = borderWidth / 2;
        int left = (int) (i * (borderWidth + spacingWidth) + (0.5 * spacingWidth));
        int right = (int) (((i + 1) * borderWidth) + (i * spacingWidth) + (0.7 * spacingWidth));
        canvas.drawCircle(circleX, circleY, dotRadius, pswDotPaint);
        drawBitmapOrBorder(canvas, left, right, height);
    }
    canvas.drawCircle((float) ((float) (((result.size() - 1) * (borderWidth + spacingWidth)) + (borderWidth / 2)) + (0.6 * spacingWidth)),
            borderWidth / 2, dotRadius, pswDotPaint);
    handler.removeMessages(1);
}

以上就是全部的繪製邏輯,計算座標這類似乎沒什麼好解釋的,接下來就是自定義鍵盤,介面不需要我們自己再重新設計,用系統的就好,來看看程式碼

鍵盤

class NumKeyListener implements OnKeyListener {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (event.isShiftPressed()) {//處理*#等鍵
                    return false;
                }
                if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {//只處理數字
                    if (result.size() < pswLength) {
                        result.add(keyCode - 7);
                        invalidate();
                        ensureFinishInput();
                    }
                    return true;
                }
                if (keyCode == KeyEvent.KEYCODE_DEL) {
                    if (!result.isEmpty()) {//不為空,刪除最後一個
                        saveResult = result.size();
                        result.remove(result.size() - 1);
                        invalidate();
                    }
                    return true;
                }
                if (keyCode == KeyEvent.KEYCODE_ENTER) {
                    ensureFinishInput();
                    return true;
                }
            }
            return false;
        }

說明一下只處理數字那部分,為什麼是keyCode - 7,我們點進原始碼看看

可以看出,我們用keyCode - 7就剛好等於輸入的數字
最終效果:

結語

以上就是該自定義view的全部說明,並未貼出全部的完整程式碼,如有需要可到GitHub檢視完整原始碼,如果喜歡或覺得該控制元件對你有幫助還請點個star,文中如有錯誤還請指出,謝謝

相關推薦

Android 支付密碼輸入定義EditText實現密碼輸入功能;

剛擼出來的密碼輸入框,註釋和邏輯看著挺清晰的,一些屬性還沒有新增,下個部落格把屬性新增上去; 看一下圖: 直接看程式碼吧! import android.content.Context; import android.graphics.Canvas; import android.

delphi 定義彈出密碼輸入

function GetAveCharSize(Canvas: TCanvas): TPoint; {$IF DEFINED(CLR)} var I: Integer; Buffer: string; Size: TSize; begin SetLength(

定義view---強大密碼輸入

我司之前有個需求,要求類似支付寶那樣的密碼支付,產品要求輸入的當前字元需要是明文密碼,1s後轉換為圓點,原本想網上那麼多密碼輸入框,肯定沒問題,結果UI一出圖就懵逼了,翻遍了各個角落,都找不到類似的密碼輸入框,沒辦法,自己寫吧。 使用方法,在gradle中新增:compile ‘com.rokudoll:P

Android定義view案例一氣泡

通過之前的兩個基礎文章的學習,我們基本知道自定義view的基本流程和各方法的作用了。那麼接下來我們就要拿起畫筆去繪製我們的view了。首先老規矩把我們的學習大綱拿出來,時刻不能忘,,哈哈、 1.自定義view單純的用畫筆繪製view(死view) 2.自定義view增加手勢

Android 定義方形驗證碼輸入,仿滴滴、小籃單車

效果圖 GIF圖不是很清晰,下面是截圖 思路 1 . 每一個輸入框其實都是一個EditText,for迴圈建立並插入到LinearLayout中。 private void

Android定義方形驗證碼輸入

先來看UI給的效果圖 實現思路 繪製多個TextView控制元件用來顯示數字 繪製隱藏EditText用來接收輸入法內容(防止部分手機或輸入法監聽不到內容) 將EditText的內容顯示到TextView中 程式碼實現 自定義控制元件繼

Android定義數字驗證碼輸入

先上效果圖 設計思路 剛開始想過使用EditText來實現,但是具體實施時發現並不是這麼容易,而且還有一堆的坑,不如直接繼承View自定義來的方便,先在onDraw方法中繪製邊框及驗證碼,調整彈出輸入法只能輸入數字,監聽輸入法輸入,每輸入一個字元都需要重

Android基礎——定義EditTExt實現去掉輸入新增下劃線

使用場景 在實際開發中我們往往需要自定義EditText,去掉輸入框,在文字下面新增下劃線,在本章中使用自定義TditeText 實現。 實現效果 實現步驟 1、Attributes實現 <?xml version="1.0" encoding="utf-8"?> <resource

定義純數字密碼輸入鍵盤-仿支付寶數字密碼鍵盤

前陣子遇到專案需求,需要自己整一個純數字密碼輸入鍵盤,所以也就整出來了,如下圖: 其實也就是繼承重寫了popupwindow。 public class MyPopupWindow extends PopupWindow implements View.OnClick

Android 定義 View——手勢密碼

    Android 自定義 View 當然是十分重要的,筆者這兩天寫了一個自定義 View 的手勢密碼,和大家分享分享:    首先,我們來建立一個表示點的類,Point.java:public class Point { // 點的三種狀態 publi

定義控制元件之 PasswordEditText(密碼輸入

前兩天在掘金上看到了一個驗證碼輸入框,然後自己實現了一下,以前都是繼承的 View,這次繼承了 ViewGroup,也算是嘗試了一點不同的東西。先看看最終效果: 事實上就是用將輸入的密碼用幾個文字框來顯示而已,要打造這樣一個東西我剛開始也是一頭霧水,不急,直接寫不會,我們可以採取曲線救

Android仿支付寶密碼輸入(定義數字鍵盤)

1.概述          Android自定義密碼輸入框,通過自定義輸入顯示框和自定義輸入鍵盤,實現仿支付寶數字鍵盤等。程式碼已託管到github,有需要的話可以去我的github下載。 可以自定義關閉圖示、文字內容、顏色、大小,彈框樣

Flutter仿微信,支付寶密碼輸入+定義鍵盤

大家好,我又來了。 今年這個冬天真的是“寒冬”啊,我是真的被“凍傷”了,一年的計劃全部被打算了,賊無奈,也讓我遭受了一定的打擊,希望之光在哪?(吐槽到此為止) 回到咱們的正題,剛用Flutter做完一個金融專案,當中使用到了類似於微信,和支付寶的那種密碼輸入框,然後為了安全一點也自己實現了自定義的鍵盤,今天跟

定義Android Dialog EditText 密碼輸入

在一些需要輸入支付密碼的需求中,如微信支付的黑圓點。 開始也是一直在想著怎麼自定義EditText去做,確實網上已經有自定義view的實現。之前也是參考別人寫的,並修改了部分後用於專案中,大家可以搜下“PasswordInputView”,好多作者實現了,大家的思路都

Android輸入帶刪除按鈕的定義View

廢話不多說,用最少的程式碼來實現,記得弄一張名字叫 common_ic_delete 做完刪除的圖到drawable裡。 package com.aiitec.widgets; import android.content.Context; import and

【Android定義控制元件】密碼輸入+數字鍵盤的實現

因專案需要,實現了一個自定義的密輸入框和自定義數字鍵盤,用作使用者支付密碼設定介面。先上效果圖如下,方格樣式,以及點選空白處隱藏軟鍵盤。 控制元件實現清單: 1)集成於EditText的輸入框控制元件:PasswordInputView.java 2)

定義密碼輸入

前言:看了好多自定義控制元件說實話道理上也可以說出一下但是實際上讓我實實在在的想要做出一個成功和考慮齊全的view來說還是有些困難,文章也許有所借鑑,但確實是自己真真實實弄出來的,主要是記錄一些實際的經驗和點點滴滴,專案暫時不算緊急,提升自己隨時隨刻 首先,貼

Android定義View之六位密碼

今天是我第一次寫技術部落格,本人也是小菜鳥一枚,工作不滿一年,部落格內容也比較簡單,對專業知識瞭解不夠深入,寫部落格的原因一來是為了分享,而來也是激勵自己,如果內容有什麼錯誤問題,請大家指教糾正。 今天要講的是六位密碼輸入框。先上程式碼。首先是layout.xml檔案 &

學習筆記-定義密碼輸入定義數字密碼軟鍵盤

      最近專案裡有一個支付功能,需要自定義鍵盤,於是我在網上搜了一下,發現這個和我需求很相符,等專案完工,打算分享給大家,卻找不到專案的博主了,這裡還是感謝博主,我就直接貼程式碼分享給大家了; XNumberKeyboardView.java import andr

Android定義View——定義搜尋(SearchView) 非常實用的控制元件

好多東西寫起來太麻煩了,而且我在最開始用的也不是自己寫的,所以找了一個非常棒的測試了一下.  轉載的 在 Android開發中,當系統資料項比較多時,常常會在app新增搜尋功能,方便使用者能快速獲得需要的資料。搜尋欄對於我們並不陌生,在許多app都能見到它,比如豌