溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android怎么自定義View實現(xiàn)隨機數(shù)驗證碼

發(fā)布時間:2022-07-01 10:25:55 來源:億速云 閱讀:108 作者:iii 欄目:開發(fā)技術

本篇內容介紹了“Android怎么自定義View實現(xiàn)隨機數(shù)驗證碼”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    效果

    Android怎么自定義View實現(xiàn)隨機數(shù)驗證碼

    自定義 View 分類

    簡單介紹一下自定義 View 分類:

    • 組合控件,繼承自已有的 layout ,比如 LinearLayout,然后通過 LayoutInflater 引入布局,然后處理相關事件,這種方式的好處在于,不需要過度關注 view 內部的繪制機制,而且擴展性也很強。

    • 繼承自現(xiàn)有的系統(tǒng)控件,修改或擴展某些屬性或方法即可,比如 AppCompatTextView

    • 繼承自 view 或 viewgroup ,這種相對要復雜一些,因為我們要自己控制繪制流程,但是相對的,也有更大的想象空間。

    步驟

    先分析一下上圖中的效果:

    • 帶顏色的矩形背景

    • 居中的文本

    比較簡單,老手稍微想一下就已經(jīng)有思路了:

    • 1.自定義屬性

    • 2.添加構造方法

    • 3.在構造里獲取自定義樣式

    • 4.重寫 onDraw 計算坐標繪制

    • 5.重寫 onMeasure 測量寬高

    • 6.設置點擊事件

    先分析效果圖,然后構思,隨后不斷的調整優(yōu)化。

    1.自定義屬性

    這一步也不一定非要寫在前面,可能有些人覺得不一定就能事先知道會用到哪些屬性,由于例子比較簡單,暫且放在前面吧,看個人習慣。

    在 res/values/ 下建立一個 attrs.xml 文件 , 在里面定義我們的 屬性 和聲明我們的整個樣式

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="randomText" format="string"/>
        <attr name="randomTextColor" format="color"/>
        <attr name="randomTextSize" format="dimension"/>
    
        <declare-styleable name="RandomTextView" >
            <attr name="randomText"/>
            <attr name="randomTextColor"/>
            <attr name="randomTextSize"/>
        </declare-styleable>
    
    </resources>

    format 是值該屬性的取值類型:一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag

    在 xml 布局中的引用:

     <com.yechaoa.customviews.randomtext.RandomTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="20dp"
            app:randomText="1234"
            app:randomTextColor="@color/colorAccent"
            app:randomTextSize="50sp" />

    注意引入 命名空間 :

    xmlns:app="http://schemas.android.com/apk/res-auto"

    2.添加構造方法

    新建一個 RandomTextView 類,繼承 View ,并添加3個構造方法

    class RandomTextView : View {
    
        //文本
        private var mRandomText: String
    
        //文本顏色
        private var mRandomTextColor: Int = 0
    
        //文本字體大小
        private var mRandomTextSize: Int = 0
    
        private var paint = Paint()
        private var bounds = Rect()
    
        //調用兩個參數(shù)的構造
        constructor(context: Context) : this(context, null)
    
        //xml默認調用兩個參數(shù)的構造,再調用三個參數(shù)的構造,在三個參數(shù)構造里獲取自定義屬性
        constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
    
        constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
            ...
        }
        ...
    }

    這里要注意的是,所有的構造方法,都指向的是第三個構造方法,前兩個構造的繼承是 this ,而不是 super 。

    第一個構造比如我們可以是 new 創(chuàng)建的,第二個是 xml 中默認調用的,我們在第三個構造中去獲取自定義屬性。

    3.在構造里獲取自定義樣式

      constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
            //獲取自定義屬性
            val typedArray = context.theme.obtainStyledAttributes(
                attributeSet,
                R.styleable.RandomTextView,
                defStyle,
                0
            )
    
            mRandomText = typedArray.getString(R.styleable.RandomTextView_randomText).toString()
            mRandomTextColor = typedArray.getColor(R.styleable.RandomTextView_randomTextColor, Color.BLACK)//默認黑色
            mRandomTextSize = typedArray.getDimensionPixelSize(
                R.styleable.RandomTextView_randomTextSize,
                TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16F, resources.displayMetrics ).toInt()
            )
    
            //獲取完回收
            typedArray.recycle()
    
            paint.textSize = mRandomTextSize.toFloat()
    
            //返回文本邊界,即包含文本的最小矩形,沒有所謂“留白”,返回比measureText()更精確的text寬高,數(shù)據(jù)保存在bounds里
            paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)
        }

    通過 obtainStyledAttributes 獲取自定義屬性,返回一個 TypedArray ,這里用到了我們在 attrs.xml 文件中聲明的樣式(R.styleable.RandomTextView),返回的 TypedArray 即包含了這里面的屬性。

    拿到自定義 view 屬性集合,然后賦值,賦值之后就可以用 paint 去畫了。

    然后用到了 paint 的 getTextBounds 方法:

    paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds)

    簡單理解就是,把文字放在一個矩形里,通過矩形的寬高即可知道文字的寬高,所以寬高會保存在 bounds 里,bounds 是一個矩形 Rect ,為什么要這么做呢,因為后面我們要計算文字居中的時候會用到。

    ok,接下來開始畫布局。

    4.重寫 onDraw 計算坐標繪制

     @SuppressLint("DrawAllocation")
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
    
            /**
             * 自定義View時,需要我們自己在onDraw中處理padding,否則是不生效的
             * 自定義ViewGroup時,子view的padding放在onMeasure中處理
             */
    
            /**
             * 矩形背景
             */
            paint.color = Color.YELLOW
            //計算坐標,因為原點是在文字的左下角,左邊要是延伸出去就還要往左邊去,所以是減,右邊和下邊是正,所以是加
            canvas?.drawRect(
                (0 - paddingLeft).toFloat(),
                (0 - paddingTop).toFloat(),
                (measuredWidth + paddingRight).toFloat(),
                (measuredHeight + paddingBottom).toFloat(),
                paint
            )
    
            /**
             * 文本
             */
            paint.color = mRandomTextColor
            //注意這里的坐標xy不是左上角,而是左下角,所以高度是相加的,在自定義view中,坐標軸右下為正
            //getWidth 等于 measuredWidth
            canvas?.drawText(
                mRandomText,
                (width / 2 - bounds.width() / 2).toFloat(),
                (height / 2 + bounds.height() / 2).toFloat(),
                paint
            )
        }

    上面的代碼就是在 onDraw 里面顯示繪制了一個 YELLOW 顏色的矩形背景,然后繪制了一個自定義屬性顏色的居中的文本。

    這里要注意我們計算位置時的 坐標 ,在自定義 view 中,原點是 view 的 左上角 ,而在數(shù)學坐標系中,原點(0,0)是在 中間 的,二者是有區(qū)別的。

    其次,假如 xml 布局中有 padding ,或者預判會使用到 padding,在重寫 onDraw 的時候也要把 padding 的數(shù)據(jù)加上,否則 padding 是不生效的。如果是繼承 ViewGroup 時, 子view 的 padding 放在 onMeasure 中處理。

    來看此時的效果:

    Android怎么自定義View實現(xiàn)隨機數(shù)驗證碼

    此時是不是有疑惑,xml 里面的寬高明明是 wrap_content ,為什么會充滿父布局呢?

    這就涉及到 onMeasure 的知識點了,往下看。

    5.重寫 onMeasure 測量寬高

    我們在 xml 設置 view 寬高有 3 種方式:

    • match_parent

    • wrap_content

    • 具體數(shù)據(jù),比如 100dp

    onMeasure 中 MeasureSpec 的 mode 也有 3 種模式:

    • EXACTLY:一般是設置了明確的值或者是 MATCH_PARENT

    • AT_MOST:表示子布局限制在一個最大值內,一般為 WARP_CONTENT

    • UNSPECIFIED:表示子布局想要多大就多大,很少使用

    由于我們 xml 用的是 wrap_content ,也就是對應 AT_MOST ,所以效果就是會占滿父布局中的 可用空間 ,而父布局是填充屏幕,所以我們自定義的 view 也會占滿全屏。

    而我們實際想要的效果是 view 包裹自己,而不是鋪滿全屏,所以我們需要在 onMeasure 中進行處理

      override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    
            /**
             * EXACTLY:一般是設置了明確的值或者是MATCH_PARENT
             * AT_MOST:表示子布局限制在一個最大值內,一般為WARP_CONTENT
             * UNSPECIFIED:表示子布局想要多大就多大,很少使用
             */
            val widthMode = MeasureSpec.getMode(widthMeasureSpec)
            val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    
            val heightMode = MeasureSpec.getMode(heightMeasureSpec)
            val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    
            var width = 0
            var height = 0
    
            //如果指定了寬度,或不限制寬度,用可用寬度即可,如果是WARP_CONTENT,則用文本寬度,再加上左右padding
            when (widthMode) {
                MeasureSpec.UNSPECIFIED,
                MeasureSpec.EXACTLY -> {
                    width = widthSize + paddingLeft + paddingRight
                }
                MeasureSpec.AT_MOST -> {
                    width = bounds.width() + paddingLeft + paddingRight
                }
            }
    
            //如果指定了高度,或不限制高度,用可用高度即可,如果是WARP_CONTENT,則用文本高度,再加上上下padding
            when (heightMode) {
                MeasureSpec.UNSPECIFIED,
                MeasureSpec.EXACTLY -> {
                    height = heightSize + paddingTop + paddingBottom
                }
                MeasureSpec.AT_MOST -> {
                    height = bounds.height() + paddingTop + paddingBottom
                }
            }
    
            //保存測量的寬高
            setMeasuredDimension(width, height)
        }

    上面的代碼呢,主要做了兩件事:

    • 獲取 view 寬高的模式

    • 針對不同的模式,對寬高進行重新測量

    最后別忘記調用 setMeasuredDimension 保存新測量的寬高,否則沒用哦。

    此時再看效果就是效果圖中的樣子了。

    6.設置點擊事件

    ok,到這,view 已經(jīng)繪制完成了,但是還沒有事件,我們在構造中加一個 點擊事件

    constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) {
            ...
            /**
             * 添加點擊事件
             */
            this.setOnClickListener {
                mRandomText = randomText()
                //更新
                postInvalidate()
            }
        }

    randomText 方法:

     /**
         * 根據(jù)文本長度 隨意數(shù)字
         */
        private fun randomText(): String {
            val list = mutableListOf<Int>()
            for (index in mRandomText.indices) {
                list.add(Random.nextInt(10))
            }
            val stringBuffer = StringBuffer()
            for (i in list) {
                stringBuffer.append("" + i)
            }
            return stringBuffer.toString()
        }

    觸發(fā)事件之后,文字更新,然后 view 重繪 更新 頁面即可。

    關于數(shù)據(jù)獲取,也就是變化后的數(shù)字,可以寫個 onTextChanged 接口,也可以寫個開放 方法 獲取。

    “Android怎么自定義View實現(xiàn)隨機數(shù)驗證碼”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

    AI