Android View教程之自定義驗(yàn)證碼輸入框效果
前言
首先,我們來看看實(shí)現(xiàn)的是怎么樣的效果:
如果我們拿到這樣的UI,想到的布局應(yīng)該是用4個(gè)EditText包在橫向的LinearLayout里面,但今天要講的View,所以我們決定用一個(gè)自定義的EditText 畫出來。
學(xué)到什么?
- 基本理解畫布概念
- 畫布的狀態(tài)、平移
- 布局測(cè)量
- 畫圖片
功能需求
- 高亮當(dāng)前輸入框
- 輸入滿4個(gè)數(shù)字自動(dòng)調(diào)用方法
思路
完全重畫一個(gè)EditText,就包含了測(cè)量布局和重新繪制這兩個(gè)關(guān)鍵步驟。好了,到這里理一下整體的思路:
- 根據(jù)驗(yàn)證碼個(gè)數(shù)以及邊框大小來計(jì)算輸入框顯示的寬度
- 覆蓋原來的EditText畫布,重新繪制方框
- 根據(jù)輸入的索引來確定高亮的方框
- 重寫onTextChanged 但滿足驗(yàn)證碼個(gè)數(shù)的時(shí)候調(diào)用自動(dòng)完成方法
開始動(dòng)手
準(zhǔn)備開始了,果斷繼承一個(gè)AppCompatEditText 來初始化基本參數(shù)先:
- 驗(yàn)證碼個(gè)數(shù)
- 輸入方框的大小
- 邊框的大小及間距
/** * 驗(yàn)證碼輸入框,重寫EditText的繪制方法實(shí)現(xiàn)。 * @author RAE */ public class CodeEditText extends AppCompatEditText { // 驗(yàn)證碼文本顏色 private int mTextColor; // 輸入的最大長(zhǎng)度 private int mMaxLength = 4; // 邊框?qū)挾? private int mStrokeWidth; // 邊框高度 private int mStrokeHeight; // 邊框之間的距離 private int mStrokePadding = 20; // 用矩形來保存方框的位置、大小信息 private final Rect mRect = new Rect(); // 方框的背景 private Drawable mStrokeDrawable; /** * 構(gòu)造方法 * */ public CodeEditText(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CodeEditText); int indexCount = typedArray.getIndexCount(); for (int i = 0; i < indexCount; i++) { int index = typedArray.getIndex(i); if (index == R.styleable.CodeEditText_strokeHeight) { this.mStrokeHeight = (int) typedArray.getDimension(index, 60); } else if (index == R.styleable.CodeEditText_strokeWidth) { this.mStrokeWidth = (int) typedArray.getDimension(index, 60); } else if (index == R.styleable.CodeEditText_strokePadding) { this.mStrokePadding = (int) typedArray.getDimension(index, 20); } else if (index == R.styleable.CodeEditText_strokeBackground) { this.mStrokeDrawable = typedArray.getDrawable(index); } else if (index == R.styleable.CodeEditText_strokeLength) { this.mMaxLength = typedArray.getInteger(index, 4); } } typedArray.recycle(); if (mStrokeDrawable == null) { throw new NullPointerException("stroke drawable not allowed to be null!"); } setMaxLength(mMaxLength); setLongClickable(false); // 去掉背景顏色 setBackgroundColor(Color.TRANSPARENT); // 不顯示光標(biāo) setCursorVisible(false); } @Override public boolean onTextContextMenuItem(int id) { return false; } /** * 設(shè)置最大長(zhǎng)度 */ private void setMaxLength(int maxLength) { if (maxLength >= 0) { setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); } else { setFilters(new InputFilter[0]); } } }
開始測(cè)量布局
初始化完了就要開始測(cè)量布局了,計(jì)算公式為:
輸入框?qū)挾?= 邊框?qū)挾?* 數(shù)量 + 邊框間距 *(數(shù)量-1)
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 當(dāng)前輸入框的寬高信息 int width = getMeasuredWidth(); int height = getMeasuredHeight(); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 判斷高度是否小于推薦高度 if (height < mStrokeHeight) { height = mStrokeHeight; } // 輸入框?qū)挾?= 邊框?qū)挾?* 數(shù)量 + 邊框間距 *(數(shù)量-1) int recommendWidth = mStrokeWidth * mMaxLength + mStrokePadding * (mMaxLength - 1); // 判斷寬度是否小于推薦寬度 if (width < recommendWidth) { width = recommendWidth; } widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode); heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightMode); // 設(shè)置測(cè)量布局 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); }
畫家登場(chǎng)
來到最重要的步驟了,重畫輸入框!來一步步看代碼注釋:
@Override protected void onDraw(Canvas canvas) { // 在畫支持設(shè)置文本顏色,把系統(tǒng)化的文本透明掉,相當(dāng)于覆蓋 mTextColor = getCurrentTextColor(); setTextColor(Color.TRANSPARENT); // 系統(tǒng)畫的方法 super.onDraw(canvas); // 重新設(shè)置文本顏色 setTextColor(mTextColor); // 重繪背景顏色 drawStrokeBackground(canvas); // 重繪文本 drawText(canvas); }
繪制背景方框
/** * 繪制方框 */ private void drawStrokeBackground(Canvas canvas) { // 下面繪制方框背景顏色 // 確定反饋位置 mRect.left = 0; mRect.top = 0; mRect.right = mStrokeWidth; mRect.bottom = mStrokeHeight; int count = canvas.getSaveCount(); // 當(dāng)前畫布保存的狀態(tài) canvas.save(); // 保存畫布 for (int i = 0; i < mMaxLength; i++) { mStrokeDrawable.setBounds(mRect); // 設(shè)置位置 mStrokeDrawable.setState(new int[]{android.R.attr.state_enabled}); // 設(shè)置圖像狀態(tài) mStrokeDrawable.draw(canvas); // 畫到畫布上 // 確定下一個(gè)方框的位置 float dx = mRect.right + mStrokePadding; // X坐標(biāo)位置 // 保存畫布 canvas.save(); // [注意細(xì)節(jié)] 移動(dòng)畫布到下一個(gè)位置 canvas.translate(dx, 0); } // [注意細(xì)節(jié)] 把畫布還原到畫反饋之前的狀態(tài),這樣就還原到最初位置了 canvas.restoreToCount(count); // 畫布?xì)w位 canvas.translate(0, 0); // 下面繪制高亮狀態(tài)的邊框 // 當(dāng)前高亮的索引 int activatedIndex = Math.max(0, getEditableText().length()); mRect.left = mStrokeWidth * activatedIndex + mStrokePadding * activatedIndex; mRect.right = mRect.left + mStrokeWidth; mStrokeDrawable.setState(new int[]{android.R.attr.state_focused}); mStrokeDrawable.setBounds(mRect); mStrokeDrawable.draw(canvas); }
一般畫布的移動(dòng)canvas.translate(x,y)會(huì)結(jié)合canvas.save();來使用。
1、調(diào)用canvas.save();保存當(dāng)前畫布的狀態(tài),用PS來解析就是按下ctrl +s鍵,然后幫你新建一個(gè)新的圖層。你之后畫的內(nèi)容不會(huì)影響到之前畫的內(nèi)容,要回到之前的狀態(tài)就調(diào)用canvas.restoreToCount(count)來還原。
2、把畫布的位置移到下一個(gè)位置canvas.translate(x,y),下圖所示,你會(huì)發(fā)現(xiàn)方框在畫布中的位置沒有發(fā)生變化而是畫布距離發(fā)生了變化。這就是畫布平移的效果了。
畫驗(yàn)證碼文字
/** * 重繪文本 */ private void drawText(Canvas canvas) { int count = canvas.getSaveCount(); canvas.translate(0, 0); int length = getEditableText().length(); for (int i = 0; i < length; i++) { String text = String.valueOf(getEditableText().charAt(i)); TextPaint textPaint = getPaint(); textPaint.setColor(mTextColor); // 獲取文本大小 textPaint.getTextBounds(text, 0, 1, mRect); // 計(jì)算(x,y) 坐標(biāo) int x = mStrokeWidth / 2 + (mStrokeWidth + mStrokePadding) * i - (mRect.centerX()); int y = canvas.getHeight() / 2 + mRect.height() / 2; canvas.drawText(text, x, y, textPaint); } canvas.restoreToCount(count); }
監(jiān)聽文本變化回調(diào)自動(dòng)完成方法
@Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text, start, lengthBefore, lengthAfter); // 當(dāng)前文本長(zhǎng)度 int textLength = getEditableText().length(); if (textLength == mMaxLength) { hideSoftInput(); if (mOnInputFinishListener != null) { mOnInputFinishListener.onTextFinish(getEditableText().toString(), mMaxLength); } } }
查看完整的源碼
到這里你能大概理解畫布的概念了,本文完。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android使用Websocket實(shí)現(xiàn)聊天室
這篇文章主要為大家詳細(xì)介紹了Android使用Websocket實(shí)現(xiàn)聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Android字體相關(guān)知識(shí)總結(jié)
最近接到一個(gè)需求,大致內(nèi)容是:全局替換當(dāng)前項(xiàng)目中的默認(rèn)字體,并引入 UI 設(shè)計(jì)師提供的一些新字體。于是對(duì)字體做了些研究,把自己的一些心得分享給大家。注意:本文所展示的系統(tǒng)源碼都是基于Android-30 ,并提取核心部分進(jìn)行分析2021-06-06Android 自定義view和屬性動(dòng)畫實(shí)現(xiàn)充電進(jìn)度條效果
近期項(xiàng)目中需要使用到一種類似手機(jī)電池充電進(jìn)度的動(dòng)畫效果,以前沒學(xué)屬性動(dòng)畫的時(shí)候,是用圖片+定時(shí)器的方式來完成的,下面給大家分享android自定義view和屬性動(dòng)畫實(shí)現(xiàn)充電進(jìn)度條2016-12-12android 仿QQ動(dòng)態(tài)背景、視頻背景的示例代碼
本篇文章主要介紹了android 仿QQ動(dòng)態(tài)背景、視頻背景的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03Android啟動(dòng)初始化方案App StartUp的應(yīng)用詳解
這篇文章主要介紹了Android啟動(dòng)初始化方案App StartUp的使用方法,StartUp是為了App的啟動(dòng)提供的一套簡(jiǎn)單、高效的初始化方案,下面我們來詳細(xì)了解2022-09-09Android實(shí)現(xiàn)懸浮窗的簡(jiǎn)單方法實(shí)例
相信大家應(yīng)該也都發(fā)現(xiàn)了,現(xiàn)在很多應(yīng)用都使用到懸浮窗,例如微信在視頻的時(shí)候,點(diǎn)擊Home鍵,視頻小窗口仍然會(huì)在屏幕上顯示,下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)懸浮窗的簡(jiǎn)單方法,需要的朋友可以參考下2021-09-09Android開發(fā)環(huán)境安裝和配置圖文教程
輕松搞定Android開發(fā)環(huán)境部署,這篇文章主要為大家詳細(xì)介紹了Android開發(fā)環(huán)境安裝和配置圖文教程,感興趣的小伙伴們可以參考一下2016-06-06Android自定義可點(diǎn)擊的ImageSpan并在TextView中內(nèi)置View
這篇文章主要為大家詳細(xì)介紹了Android自定義可點(diǎn)擊的ImageSpan并在TextView中內(nèi)置"View",具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11