實現(xiàn)一個UI審美的驗證碼輸入框

抽空實現(xiàn)了一個驗證碼輸入框的自定義View吐绵,效果圖如下迹淌,其中每一個小圓圈都是一個selector,所以動態(tài)修改只需要改動drawable的內(nèi)容即可實現(xiàn)修改己单,這樣可發(fā)揮的內(nèi)容也就很多唉窃。
WechatIMG4.jpeg
WechatIMG4.jpeg

分析

繼承關(guān)系

首先是一個輸入框,如果要手動實現(xiàn)一個輸入框View未免顯得太過麻煩纹笼,所以這里直接繼承自AppCompatEditText實現(xiàn)纹份。

public class VerifyCodeView extends AppCompatEditText{
      public VerifyCodeView(Context context) {
        this(context, null);
    }

    public VerifyCodeView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.editTextStyle);
    }

    @SuppressLint("ResourceAsColor")
    public VerifyCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerifyCodeView);
        verifyCodeLength = typedArray.getInteger(R.styleable.VerifyCodeView_verifyCodeLength, 4);
        strokeWidth = (int) typedArray.getDimension(R.styleable.VerifyCodeView_strokeWidth, ScreenUtil.dip2px(App.context, 50));
        strokeHeight = (int) typedArray.getDimension(R.styleable.VerifyCodeView_strokeHeight, ScreenUtil.dip2px(App.context, 50));
        strokePadding = (int) typedArray.getDimension(R.styleable.VerifyCodeView_strokePadding, ScreenUtil.dip2px(App.context, 30));
        strokeBackground = typedArray.getDrawable(R.styleable.VerifyCodeView_strokeBackground);
        typedArray.recycle();

        //設(shè)置數(shù)據(jù)長度
        if (verifyCodeLength >= 0) {
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(verifyCodeLength)});
        } else {
            setFilters(new InputFilter[0]);
        }
        //禁止長按
        setLongClickable(false);
        //隱藏光標(biāo)
        setCursorVisible(false);
        //背景透明
        setBackgroundColor(Color.TRANSPARENT);
    }
}

在最后一個構(gòu)造方法中,獲取到一些初始化屬性的值廷痘,然后就是設(shè)置一些必要的UI顯示蔓涧,比如說限制輸入最大長度為驗證碼長度(這邊用的是InputFilter,感興趣的自行了解下)笋额,禁止長按蠢笋,隱藏光標(biāo),EditText背景透明(隱藏底部黑線)鳞陨,都是一些簡單的配置隱藏官方給的View的UI。

測量

實現(xiàn)一個自定義View包含layout瞻惋,measure和draw三個步驟厦滤,這里layout不需要,measure是需要實現(xiàn)的歼狼,其中

  • View最小高度不得小于邊框高度掏导,這里的邊框高度就是小圓圈的直徑,如果小圓圈換成正方形就是正方形的邊長羽峰。
  • View的最小寬度不得小于(邊框?qū)挾?* 邊框數(shù))+ (邊框間距 * 邊框數(shù)減1)趟咆,這個公式也很好理解添瓷,就是算四個格子加縫隙的寬度。這里在else里面做了一個簡單的處理值纱,就是讓View整體居中顯示鳞贷,根據(jù)startPos的配置,可以從startPos位置開始畫起虐唠。
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //最小高度不得低于邊框高度
        if (height < strokeHeight) {
            height = strokeHeight;
        }

        //最小寬度不得小于(邊框?qū)挾?* 邊框數(shù))+ (邊框間距 * 邊框數(shù)減1)
        int editViewWidth = (strokeWidth * verifyCodeLength + strokePadding * (verifyCodeLength - 1));
        if (width < editViewWidth) {
            width = editViewWidth;
        } else {
            //左邊起始
            startPos = width / 2 - editViewWidth / 2;
        }

        //重新生成spec
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode);
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightMode);

        //設(shè)置重新測量
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
 }
繪制

從圖中驗證碼輸入框部分可以看到搀愧,主要分為三部分的處理,一是繪制背景圈疆偿,二是繪制選中圈咱筛,三是繪制文字所以我在onDraw()方法里繪制了這幾部分

   @Override
    protected void onDraw(Canvas canvas) {
        //獲取字體背景色
        textColor = getCurrentTextColor();
        //設(shè)置透明字體防止原來的輸入內(nèi)容顯示
        setTextColor(Color.TRANSPARENT);
        super.onDraw(canvas);
        //等原來的繪制完再設(shè)置自定義的字體顏色
        setTextColor(textColor);
        //繪制背景
        drawBackground(canvas);
        //繪制驗證碼
        drawVerifyText(canvas);
    }

注:其中textColor是緩存下當(dāng)前設(shè)置的edittext的顏色,然后設(shè)置顏色為透明杆故,這樣在之后的輸入中就不會看到EditText默認(rèn)樣子的文本迅箩,但是這樣做又會影響我們要主動繪制文本顏色,所以在默認(rèn)的繪制完之后处铛,自定義的繪制之前設(shè)置回textColor饲趋。

首先看下繪制背景,繪制背景包含兩部分罢缸,一個是背景篙贸,一個是選中的前景,其中背景就是循環(huán)部分枫疆。下面看代碼中的注釋的詳細(xì)解釋

  • 注釋1:這邊是配置一個矩形的位置爵川,這個矩形是驗證碼框的最大邊界。我們不管設(shè)置什么形狀息楔,都是在這個范圍之內(nèi)寝贡,其中animOffset是我做的一個小動畫,如果不想要的話值依,可以直接去掉圃泡。
image.png
image.png
  • 注釋2部分:確定邊框后,通過setBounds定位愿险,setState繪制選擇器中哪一個drawable颇蜡,draw繪制,save保存辆亏,translate移動到下一個位置
  • 注釋3部分:畫完之后要恢復(fù)到初始的狀態(tài),然后才能繪制后面的選中圖層风秤。
  • 注釋4部分:選中圖層類似,要確定一個矩形邊界扮叨,然后根據(jù)當(dāng)前輸入的文本長度確定繪制位置缤弦,最后進(jìn)行繪制。
 private void drawBackground(Canvas canvas) {
        //輸入框矩形
        Rect rect = new Rect();
        //獲取當(dāng)前的保存狀態(tài)
        canvas.translate(startPos, 0);
        int count = canvas.getSaveCount();
        canvas.save();

        //繪制幾個框
        for (int i = 0; i < verifyCodeLength; i++) {
            //注釋1
            rect.left = 0;
            rect.top = strokeHeight - animOffset;
            rect.right = rect.left + strokeWidth;
            rect.bottom = rect.top + strokeHeight;

            if (rect.top != 0) {
                animOffset += 2;
            }
            //注釋2
            //設(shè)置框邊界
            strokeBackground.setBounds(rect);
            //設(shè)置框狀態(tài)
            strokeBackground.setState(new int[]{android.R.attr.state_enabled});
            //繪制
            strokeBackground.draw(canvas);
            //Ctrl s
            canvas.save();
            //位移
            canvas.translate(rect.right + strokePadding, 0);
        }

        //注釋3
        canvas.restoreToCount(count);
        canvas.translate(0, 0);

        //注釋4
        if (getEditableText().length() != verifyCodeLength) {
            //確定輸入內(nèi)容長度
            int current = Math.max(0, getEditableText().length());
            //確定方框左右邊界
            rect.left = (strokeWidth + strokePadding) * current;
            rect.right = rect.left + strokeWidth;
            strokeBackground.setState(new int[]{android.R.attr.state_focused});
            strokeBackground.setBounds(rect);
            strokeBackground.draw(canvas);
        }
    }

最后是繪制文本的部分彻磁,繪制文本重點關(guān)注x碍沐,y坐標(biāo)的確定狸捅。x坐標(biāo)是這樣確定的:

image.png
image.png
首先確定每個文字中心點x坐標(biāo):strokeWidth / 2 + (strokeWidth + strokePadding) * i然后減去文字bounds的一半就是說明從“喵”的最左邊開始寫起 , y坐標(biāo)的話同理 畫布的一半的y軸坐標(biāo)就是 (bottom - top )/2, 加上文字的一半,則書寫從左下角開始寫起

  private void drawVerifyText(Canvas canvas) {
        Rect rect = new Rect();
        canvas.translate(0, 0);
        int count = canvas.getSaveCount();
        canvas.save();
        int length = getEditableText().length();
        TextPaint textPaint = getPaint();
        for (int i = 0; i < length; i++) {
            String s = String.valueOf(getEditableText().charAt(i));
            textPaint.setColor(textColor);
            textPaint.setFakeBoldText(true);
            textPaint.getTextBounds(s, 0, 1, rect);
            //計算位置
            //x坐標(biāo)以文字基線位置(左)位置為準(zhǔn)
            int x = strokeWidth / 2 + (strokeWidth + strokePadding) * i - (rect.centerX());
            //y坐標(biāo)以文字基線(下)位置為準(zhǔn)
            int y = canvas.getHeight() / 2 + rect.height() / 2;
            canvas.drawText(s, x, y, textPaint);
        }
        canvas.restoreToCount(count);
    }

在這里還有一個輸入完成的回調(diào)累提,用于給外界獲取數(shù)據(jù)尘喝。

  /**
     * 輸入內(nèi)容監(jiān)聽器
     */
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        int textLength = getEditableText().length();
        if (textLength == verifyCodeLength) {
            Systems.hideIME();
            if (dataCall != null) {
                dataCall.back(getEditableText().toString());
            }
        }
    }

    /**
     * 回調(diào)
     */
    public void setDataCall(DataCall<String> dataCall) {
        this.dataCall = dataCall;
    }

這里面有一些我自己封裝的方法,如DataCall(就是一個帶數(shù)據(jù)返回的接口)刻恭,hideIME(隱藏輸入法)等瞧省。最后貼一下selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true">
        <shape android:shape="oval">
            <stroke android:width="2dp"
                android:color="#FFFB9C00" />
            <corners android:radius="4dp" />
        </shape>
    </item>
    <item>
        <shape android:shape="oval">
            <stroke android:width="2dp" android:color="#EEEEEE" />
            <corners android:radius="4dp" />
        </shape>
    </item>
</selector>

總結(jié)

對于這樣一個自定義View,繪制的內(nèi)容比較簡單鳍贾,沒有什么復(fù)雜的操作鞍匾,重點是確定輸入框的位置。然后處理官方View所顯示的內(nèi)容骑科。相對來說還是簡單的橡淑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咆爽,隨后出現(xiàn)的幾起案子梁棠,更是在濱河造成了極大的恐慌,老刑警劉巖斗埂,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件符糊,死亡現(xiàn)場離奇詭異,居然都是意外死亡呛凶,警方通過查閱死者的電腦和手機男娄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漾稀,“玉大人模闲,你說我怎么就攤上這事≌负矗” “怎么了尸折?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殷蛇。 經(jīng)常有香客問我实夹,道長,這世上最難降的妖魔是什么粒梦? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任亮航,我火速辦了婚禮,結(jié)果婚禮上谍倦,老公的妹妹穿的比我還像新娘。我一直安慰自己泪勒,他們只是感情好昼蛀,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布宴猾。 她就那樣靜靜地躺著,像睡著了一般叼旋。 火紅的嫁衣襯著肌膚如雪仇哆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天夫植,我揣著相機與錄音讹剔,去河邊找鬼。 笑死详民,一個胖子當(dāng)著我的面吹牛延欠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沈跨,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼由捎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饿凛?” 一聲冷哼從身側(cè)響起狞玛,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涧窒,沒想到半個月后心肪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡纠吴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年硬鞍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呜象。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡膳凝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恭陡,到底是詐尸還是另有隱情蹬音,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布休玩,位于F島的核電站著淆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拴疤。R本人自食惡果不足惜永部,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呐矾。 院中可真熱鬧苔埋,春花似錦、人聲如沸蜒犯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玉工,卻和暖如春羽资,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遵班。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工屠升, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狭郑。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓腹暖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親愿阐。 傳聞我的和親對象是個殘疾皇子微服,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容

  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,444評論 0 17
  • 首先缨历,我們來看看實現(xiàn)的是怎么樣的效果: 如果我們拿到這樣的UI以蕴,想到的布局應(yīng)該是用4個EditText包在橫向的L...
    Android高級工程師閱讀 893評論 0 13
  • 我想每個寫文章的作者都有過相似情況丛肮,有時候你打開手機或者電腦,一時間失去靈感魄缚。不知道如何敲出那代表你思想的一串串文...
    JANEBURY閱讀 624評論 4 13
  • 歲月靜靜好 小步碎碎跑 跑過 春夏秋冬 還有 那 山花爛漫 靜靜香 …… 杯酒釋懷 幾...
    桓舟子閱讀 82評論 0 2
  • 對著空調(diào)吹宝与, 愜意加迷醉, 不知不覺已入睡冶匹, 夢圓與誰习劫?
    鴻怡軒閱讀 168評論 1 0