自定義View(一)繪制簡單圖形(驗證碼輸入框[仿探探])

這是一個仿照探探的驗證碼輸入控件,防止輸入溢出,支持自身屬性自定義等.
該項目以上傳至Github中,歡迎star/fork,項目地址VercideEditText

一、 項目說明

1.1 demo演示

![demo.gif](http://upload-images.jianshu.io/upload_images/2493724-d816fafc6b3d3a8d.gif?imageMogr2/auto-orient/strip =100x100)

1.2 特性

  • [x] 繼承EditText,可以當(dāng)作EditText使用
  • [x] 防止輸入溢出
  • [x] 自定義驗證碼位數(shù)
  • [x] 提供輸入內(nèi)容的監(jiān)聽器
  • [x] 高度自適配
  • [x] 屬性自定義配置

1.3 集成

JCenter方式

第一步,添加至工程的build.gradle文件中

repositories {
     jcenter()
}

第二步,添加至module的build.gradle文件中

compile 'com.justkiddingbaby:vercodeedittext:1.0.0'

1.4 屬性說明

屬性 介紹 取值
figures 驗證碼位數(shù) integer
verCodeMargin 每個驗證碼的間隔 dimension
bottomLineSelectedColor 底線選擇狀態(tài)下的顏色 reference
bottomLineNormalColor 底線未選中狀態(tài)下的顏色 reference
bottomLineHeight 底線高度 dimension
selectedBackgroundColor 選中的背景顏色 reference

1.5 使用

在布局中使用

  <com.jkb.vcedittext.VerificationCodeEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:text="123"
        android:textColor="@color/colorPrimary"
        android:textSize="40sp"
        app:bottomLineHeight="2dp"
        app:bottomLineNormalColor="@color/gravy_light"
        app:bottomLineSelectedColor="@color/colorAccent"
        app:figures="4"
        app:selectedBackgroundColor="@color/colorPrimary_alpha33"
        app:verCodeMargin="10dp" />

二、 代碼分析

下面讓我們來一步一步對該項目的源碼進行分析.

2.1 需求分析

這是一個驗證碼輸入控件,根據(jù)demo演示分析得出,需要支持的屬性有:
1、作為EditText控件使用(繼承EditText)
2、自定義驗證碼輸入位數(shù),并且支持每位驗證碼的margin自定義
3、有背景的選擇變化和底部下劃線的輸入變化,并且支持顏色自定義
4、高度自適配,wrap_content時高度和每位驗證碼寬度一樣
5、支持輸入內(nèi)容變化的監(jiān)聽

2.2 聲明接口和屬性

本節(jié)將對2.1中的需求進行分析,并定義出屬于該控件獨有的屬性。

2.2.1 屬性聲明

在values目錄下創(chuàng)建attrs.xml文件(如果無則創(chuàng)建),這是為了支持在xml中直接配置控件的相關(guān)屬性。

   <declare-styleable name="VerCodeEditText">
        <!--驗證碼位數(shù)-->
        <attr name="figures" format="integer" />
        <!--驗證碼每位之間間隔-->
        <attr name="verCodeMargin" format="dimension" />
        <!--底線選中狀態(tài)下的顏色-->
        <attr name="bottomLineSelectedColor" format="reference" />
        <!--底線未選中狀態(tài)下的顏色-->
        <attr name="bottomLineNormalColor" format="reference" />
        <!--底線高度-->
        <attr name="bottomLineHeight" format="dimension" />
        <!--驗證碼選中狀態(tài)下的背景顏色-->
        <attr name="selectedBackgroundColor" format="reference" />
    </declare-styleable>

2.2.2 接口聲明

新建一個接口類VerificationAction,(這是為了支持使用Java代碼配置控件的屬性)。

interface VerificationAction {
    /**
     * 設(shè)置位數(shù)
     */
    void setFigures(int figures);
    /**
     * 設(shè)置驗證碼之間的間距
     */
    void setVerCodeMargin(int margin);
    /**
     * 設(shè)置底部選中狀態(tài)的顏色
     */
    void setBottomSelectedColor(@ColorRes int bottomSelectedColor);
    /**
     * 設(shè)置底部未選中狀態(tài)的顏色
     */
    void setBottomNormalColor(@ColorRes int bottomNormalColor);
    /**
     * 設(shè)置選擇的背景色
     */
    void setSelectedBackgroundColor(@ColorRes int selectedBackground);
    /**
     * 設(shè)置底線的高度
     */
    void setBottomLineHeight(int bottomLineHeight);
    /**
     * 設(shè)置當(dāng)驗證碼變化時候的監(jiān)聽器
     */
    void setOnVerificationCodeChangedListener(OnVerificationCodeChangedListener listener);
    /**
     * 驗證碼變化時候的監(jiān)聽事件
     */
    interface OnVerificationCodeChangedListener {
        /**
         * 當(dāng)驗證碼變化的時候
         */
        void onVerCodeChanged(CharSequence s, int start, int before, int count);
        /**
         * 輸入完畢后的回調(diào)
         */
        void onInputCompleted(CharSequence s);
    }
}

根據(jù)上述接口和屬性的定義內(nèi)容可以看出,接口的內(nèi)容和完全和屬性向?qū)?yīng),6個屬性對應(yīng)6個set方法,另外接口中單獨定義了驗證碼輸入內(nèi)容變化的監(jiān)聽接口。

2.3 控件自定義

接下來我們就一步步地完成這個控件。

2.3.1 創(chuàng)建類,并聲明相關(guān)屬性并繼承EditText和實現(xiàn)屬性接口VerificationAction

public class VerificationCodeEditText extends android.support.v7.widget.AppCompatEditText implements VerificationAction{
    //attrs
    private int mFigures;//需要輸入的位數(shù)
    private int mVerCodeMargin;//驗證碼之間的間距
    private int mBottomSelectedColor;//底部選中的顏色
    private int mBottomNormalColor;//未選中的顏色
    private float mBottomLineHeight;//底線的高度
    private int mSelectedBackgroundColor;//選中的背景顏色

    public VerificationCodeEditText(Context context) {
        this(context, null);
    }
    public VerificationCodeEditText(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public VerificationCodeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(attrs);
    }
    /**
     * 初始化屬性
     */
    private void initAttrs(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.VerCodeEditText);
        mFigures = ta.getInteger(R.styleable.VerCodeEditText_figures, 4);
        mVerCodeMargin = (int) ta.getDimension(R.styleable.VerCodeEditText_verCodeMargin, 0);
        mBottomSelectedColor = ta.getColor(R.styleable.VerCodeEditText_bottomLineSelectedColor,
                getCurrentTextColor());
        mBottomNormalColor = ta.getColor(R.styleable.VerCodeEditText_bottomLineNormalColor,
                getColor(android.R.color.darker_gray));
        mBottomLineHeight = ta.getDimension(R.styleable.VerCodeEditText_bottomLineHeight,
                dp2px(5));
        mSelectedBackgroundColor = ta.getColor(R.styleable.VerCodeEditText_selectedBackgroundColor,
                getColor(android.R.color.darker_gray));
        ta.recycle();
    }
    ...set方法
}

上述代碼沒啥好說的,只是定義了類的基礎(chǔ)機構(gòu),關(guān)于View的三個構(gòu)造方法的區(qū)別,可以參考文章 Android自定義View構(gòu)造函數(shù)詳解

2.3.2 測量寬高

測量View的寬高需要重寫onMeasure(int,int)方法,因為本控件是繼承EditText的,而EditText默認(rèn)的測量和本需求不同,所以需要完全自己實現(xiàn)該方法,點開super.onMeasure(int,int)方法后,發(fā)現(xiàn)最終調(diào)用的方法是setMeasuredDimension(int, int),該方法的代碼如下:

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthResult = 0, heightResult = 0;
        //最終的寬度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            widthResult = widthSize;
        } else {
            widthResult = getScreenWidth(getContext());//默認(rèn)為屏幕的寬度
        }
        //每位驗證碼的寬度
        mEachRectLength = (widthResult - (mVerCodeMargin * (mFigures - 1))) / mFigures;
        //最終的高度
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            heightResult = heightSize;
        } else {
            heightResult = mEachRectLength;
        }
        setMeasuredDimension(widthResult, heightResult);
    }

該控件的寬高測量比較簡單,相信上述代碼還是比較明了的,在此就不再做相關(guān)解釋了。

2.3.3 繪制

從demo演示中可以看出,該控件并沒有什么復(fù)雜的場景,只是計算矩形的寬度并繪制矩形、文字、下劃線而已,繪制View需要重寫onDraw(Canvas)方法。
在重寫該方法之前,我們需要定義繪制時候的畫筆并進行初始化,一共要繪制的內(nèi)容有三個,矩形、文字、下劃線,而矩形和下劃線各自都有兩種狀態(tài),而文字直接可以用Canvas來繪制,則需要的畫筆一共有四個~

    private Paint mSelectedBackgroundPaint;
    private Paint mNormalBackgroundPaint;
    private Paint mBottomSelectedPaint;
    private Paint mBottomNormalPaint;

然后進行畫筆的初始化操作,畫筆的初始化可以在初始化屬性之后調(diào)用。

    private void initPaint() {
        mSelectedBackgroundPaint = new Paint();
        mSelectedBackgroundPaint.setColor(mSelectedBackgroundColor);
        mNormalBackgroundPaint = new Paint();
        mNormalBackgroundPaint.setColor(getColor(android.R.color.transparent));

        mBottomSelectedPaint = new Paint();
        mBottomNormalPaint = new Paint();
        mBottomSelectedPaint.setColor(mBottomSelectedColor);
        mBottomNormalPaint.setColor(mBottomNormalColor);
        mBottomSelectedPaint.setStrokeWidth(mBottomLineHeight);
        mBottomNormalPaint.setStrokeWidth(mBottomLineHeight);
    }

接下來,我們需要根據(jù)驗證碼的位數(shù)繪制不同的矩形顏色和下劃線顏色,定義一個全局變量mCurrentPosition 在繪制的時候使用,然后重寫onDraw(Canvas)方法。

    @Override
    protected void onDraw(Canvas canvas) {
        mCurrentPosition = getText().length(); //獲取驗證碼位數(shù)
      //支持padding屬性
        int width = mEachRectLength - getPaddingLeft() - getPaddingRight();
        int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        //循環(huán)繪制
        for (int i = 0; i < mFigures; i++) {
            canvas.save();
            int start = width * i + i * mVerCodeMargin;
            int end = width + start;
            //畫一個矩形
            if (i == mCurrentPosition) {//選中的下一個狀態(tài)
                canvas.drawRect(start, 0, end, height, mSelectedBackgroundPaint);
            } else {
                canvas.drawRect(start, 0, end, height, mNormalBackgroundPaint);
            }
            canvas.restore();
        }
        //繪制文字
        String value = getText().toString();
        for (int i = 0; i < value.length(); i++) {
            canvas.save();
            int start = width * i + i * mVerCodeMargin;
            float x = start + width / 2;
            TextPaint paint = getPaint();
            paint.setTextAlign(Paint.Align.CENTER);
            paint.setColor(getCurrentTextColor());
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            float baseline = (height - fontMetrics.bottom + fontMetrics.top) / 2
                    - fontMetrics.top;
            canvas.drawText(String.valueOf(value.charAt(i)), x, baseline, paint);
            canvas.restore();
        }
        //繪制底線
        for (int i = 0; i < mFigures; i++) {
            canvas.save();
            float lineY = height - mBottomLineHeight / 2;
            int start = width * i + i * mVerCodeMargin;
            int end = width + start;
            if (i < mCurrentPosition) {
                canvas.drawLine(start, lineY, end, lineY, mBottomSelectedPaint);
            } else {
                canvas.drawLine(start, lineY, end, lineY, mBottomNormalPaint);
            }
            canvas.restore();
        }
    }

以上代碼中繪制矩形和下劃線并沒有什么復(fù)雜的,主要是在繪制文字的時候需要注意文字的居中問題,關(guān)于該問題可以參考文章Android Canvas drawText實現(xiàn)中文垂直居中

2.3.4 防止輸入溢出

在輸入驗證碼之后,我們需要對其進行邊界控制,默認(rèn)會一直向后繪制,這樣在超出驗證碼位數(shù)后點擊刪除則不會第一時間刪除最后一位驗證碼,這也是為了邏輯的完整性,我們需要對輸入內(nèi)容進行監(jiān)聽,則需要調(diào)用方法addTextChangedListener(),該方法可以在構(gòu)造方法中進行調(diào)用。

  @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        mCurrentPosition = getText().length();
        postInvalidate();
    }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        mCurrentPosition = getText().length();
        postInvalidate();
        if (onCodeChangedListener != null) {
            onCodeChangedListener.onVerCodeChanged(getText(), start, before, count);
        }
    }
    @Override
    public void afterTextChanged(Editable s) {
        mCurrentPosition = getText().length();
        postInvalidate();
        if (getText().length() == mFigures) {
            if (onCodeChangedListener != null) {
                onCodeChangedListener.onInputCompleted(getText());
            }
        } else if (getText().length() > mFigures) {
            getText().delete(mFigures, getText().length());//防止輸入溢出
        }
    }

以上代碼在防止輸入溢出的同時監(jiān)聽了驗證碼輸入內(nèi)容變化時候的監(jiān)聽。

以上是對該控件的代碼分析過程,因為控件比較簡單,沒有進行詳細(xì)的說明,要是有問題請留言,同時該項目已經(jīng)上傳至github中,項目地址VercideEditText

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 剛看完幾集 歡樂頌。 挺羨慕劉濤和奇點在見面之前的交流方式,不知道對方性別、年齡、通訊方式等一些富有主觀因素的外在...
    鹽小茴香閱讀 247評論 0 0
  • 二十一二歲,陽光正好,三兩好友,背起行囊,開始一段新的旅程。 理想是豐滿的,現(xiàn)實是骨感的。我理想中的二十來歲的生活...
    小小小沐戈閱讀 763評論 0 0
  • 人為什么會因胖而死 體脂訓(xùn)練營學(xué)習(xí)時看到過這樣一段BBC的紀(jì)錄片,內(nèi)容是解剖一位心臟衰竭也就是心臟病去世的女士,...
    Liu劉美芳健康減脂閱讀 497評論 0 0
  • 隨手來一張閱讀 339評論 0 0

友情鏈接更多精彩內(nèi)容