大家都知道的Android自定義控件原理

Android控件架構(gòu)

每一個Activity包含一個Window對象,DecorView作為整個應(yīng)用窗口的根View,其下包含TitleView和ContentView,這里ContentView就是id為content的FrameLayout,我們平時寫的layout就是天生包裹著一層FrameLayout。在代碼中,Activity的OnCreate中調(diào)用setContentView()方法后,ActivityManagerService會回調(diào)onResume()方法,此時系統(tǒng)會將整個DecorView添加到PhoneWindow中,并讓其顯示出來,由此可以見,為了讓視圖盡快顯示,盡量減輕OnCreate操作。

View的測量

要想繪制一個View,我們必須先對其進(jìn)行測量,知道大小。View的onMeasure()方法


    /**
     * 測量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

Android系統(tǒng)給我們提供了一個工具類MeasureSpec,它是一個32位的int值,高2位是測量模式,低30位就是測量大小。測量模式分為下面三個。
1、EXACTLY
精確模式,match_parant或者具體數(shù)據(jù)100dp。
2、AT_MOST
最大值模式,此時控件尺寸不超過父控件允許的最大尺寸即可,wrap_content就是此模式。
3、UNSPECIFIED
View想多大就多大。

View默認(rèn)提供了EXACTLY 模式,想要支持AT_MOST和UNSPECIFIED,必須自己自定義了。

系統(tǒng)最終還是會調(diào)用setMeasuredDimension()這個方法將測量的寬高設(shè)置進(jìn)去從而完成測量工作

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

模板代碼,測量寬(測量高類似) 代碼注釋詳細(xì):

 private int measureWidth(int measureSpec) {
        int result = 0;
        //測量模式
        int specMode = MeasureSpec.getMode(measureSpec);
        //測量大小
        int specSize = MeasureSpec.getSize(measureSpec);

        //如果模式是精確模式,則測量結(jié)果為測量出的大小specSize 
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {//不是精確模式,則給定一個值,如果是AT_MOST則選則小的作為最終測量大小。
            result = 100;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

View的繪制

通過canvas的draw方法進(jìn)行繪制

Canvas canvas = new Canvas(Bitmap);

//繪制直線
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

//繪制矩形
canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

//繪制圓形
canvas.drawCircle(float cx, float cy, float radius, Paint paint);

//繪制字符
canvas.drawText(String text, float x, float y, Paint paint);

//繪制圖形
canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);

ViewGroup的測量

ViewGroup 可以放置n個View,當(dāng)ViewGroup時wrap_content模式,那么其大小是通過ViewGroup遍歷所有的子View,來獲取View的大小,從而決定自身的大小,而在其他模式下,會通過具體的值來自定自身的大小。ViewGroup遍歷所有的View會調(diào)用所有的View的onMeasure()方法來獲取測量結(jié)果,

當(dāng)子View測量完畢之后,,就需要將子View放在合適的地方,這部分是由onLayout()來進(jìn)行的,

在我們自定義ViewGroup的時候,一般都要重寫onLayout()方法控制子View顯示位置的邏輯,同樣,如果需要wrap_content屬性,那就必須重寫onMeasure方法了,這點和View是相同的

View的繪制

ViewGroup是通過dispathDraw來繪制其子View的,其過程也是通過遍歷所有子View,然后調(diào)用子View的繪制方法完成繪制的

自定義View

通常情況下自定義View有三種方法
1、對現(xiàn)有控件進(jìn)行擴(kuò)展

2、通過組合來實現(xiàn)新控件

3、重寫View實現(xiàn)全新的控件

在View中通常有一些比較重要的回調(diào)方法來。
onFinishInflate();從XML加載組件后回調(diào)。

onSizeChanged();組件大小改變時回調(diào)

onMeasure();測量

onLayout();繪制位置

onTouchEvent();監(jiān)聽觸摸事件

事件攔截機(jī)制分析

假設(shè)一個場景:
BOSS -- ViewGroupA 最外層的
項目負(fù)責(zé)人 -- ViewGroupB 中間層的
程序猿 -- View 最底層

對于ViewGroup一般有一下三個方法

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

對于View一般重寫以下兩個方法:

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

對比下看到ViewGroup比View多了一個方法onInterceptTouchEvent,事件攔截的核心方法。
什么都不做,正常情況是:
事件傳遞順序:BOSS - 項目負(fù)責(zé)人 - 程序猿,傳遞過程中會先執(zhí)行dispatchTouchEvent(事件分發(fā)),在執(zhí)行onInterceptTouchEvent(攔截)。
事件處理順序:程序猿 - 項目負(fù)責(zé)人 - BOSS 。事件處理調(diào)用的是onTouchEvent。

onInterceptTouchEvent返回True則表示攔截,不讓下分發(fā),自己搞起;返回false則表示分發(fā)到下一層中,依次傳遞。
onTouchEvent 返回true表示已處理,不用上層處理;false表示上交上層處理。

簡單來說,BOSS自己想搞起,就把onInterceptTouchEvent返回true,那么就沒有項目負(fù)責(zé)人和程序猿鳥事了;
BOSS不想搞,就把onInterceptTouchEvent返回false,則分發(fā)給項目負(fù)責(zé)人,如果項目負(fù)責(zé)人想搞,則onInterceptTouchEvent返回true,那就沒有程序猿鳥事;如果項目負(fù)責(zé)人也不想搞,則onInterceptTouchEvent返回tfalse,那么苦逼的程序猿只能搞起了。

當(dāng)程序猿加班加點搞定了,不想提交上層,則onTouchEvent返回true即可,否則提交給項目負(fù)責(zé)人,依次類推;

好了好了,View和ViewGrop的一些原理都分析了一下,其實復(fù)雜的自定義View就是通過這些基礎(chǔ)一點點寫出來的,看完這篇小文,大家可以去找一下開源的自定義View進(jìn)行驗證學(xué)習(xí)。

最后編輯于
?著作權(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)容

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