Android-控件架構(gòu)

Android-控件架構(gòu)

Android的控件是Android的血與肉现恼;本篇會講解Android的View架構(gòu),view的測量與繪制,自定義view和控件的事件分發(fā)攔截機制

控件架構(gòu)

1.View的測量

在OnMeasure()方法中進(jìn)行龟梦,Android提供了一個短小但強大的類MeasureSpec(),通過它來幫助測量View夺欲。MeasureSpec是一個32位的Int值谓罗,高2位是測量的模式,低30位是測量的大小樊拓,使用這種位運算是為了提高測量的效率纠亚。

測量模式

  • EXACTLY
    表示父視圖希望子視圖的大小應(yīng)該是由specSize的值來決定的,系統(tǒng)默認(rèn)會按照這個規(guī)則來設(shè)置子視圖的大小筋夏,開發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小蒂胞。

  • AT_MOST
    表示子視圖最多只能是specSize中指定的大小,開發(fā)人員應(yīng)該盡可能小得去設(shè)置這個視圖条篷,并且保證不會超過specSize骗随。系統(tǒng)默認(rèn)會按照這個規(guī)則來設(shè)置子視圖的大小,開發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小赴叹。

  • UNSPECIFIED
    表示開發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小鸿染,沒有任何限制。這種情況比較少見乞巧,不太會用到涨椒。

View默認(rèn)的onMeasure只支持EXACTLY模式

直接繼承View,就不能用wrap_content屬性绽媒;想用就必須重寫onMeasure方法

通過MeasureSpec這個類蚕冬,我們就獲取到了View的測量模式和繪制的大小,有了這些信息我們就可以控制View最后顯示的大小了

首先要知道重寫OnMeasure方法還是調(diào)用的父類的方法是辕,而父類又是調(diào)用setMeasuredDimension方法

//繼承View囤热,重寫OnMeasure方法
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

//點進(jìn)上面OnMeasure中,View類的OnMeasure方法就是又調(diào)用setMeasuredDimension方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

例子获三,自定義View旁蔼,繼承View,實現(xiàn)使用wrap_content屬性:


public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //由于我們直接調(diào)用setMeasuredDimension方法疙教,就不需要調(diào)用父類的方法了棺聊,可以注釋掉
        setMeasuredDimension(measrueWidth(widthMeasureSpec), measrueHeight(heightMeasureSpec));
    }

    //測量寬度,先判斷模式為EXACTLY
    private int measrueWidth(int widthMeasureSpec) {
        int result = 0;
        //獲取模式是EXACTLY還是其他模式
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        //獲取控件大小
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            //如果不是EXACTLY模式則需要設(shè)置一個默認(rèn)大小
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                //取其中小的一個
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    //測量高度贞谓,類似寬度躺屁,如上
    private int measrueHeight(int heightMeasureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

}

2.View的繪制

重寫View的OnDraw()方法,介紹一下Canvas经宏,就是一個畫布犀暑,有了它就可以繪圖了驯击。

Canvas mCanvas = new Canvas(bitmap);

傳遞一個bitmap是為了記錄保存下繪圖的像素信息,有了這些信息才能進(jìn)行繪制耐亏,兩者緊緊聯(lián)系在一起的徊都。也就是說傳的bitmap值不同,畫的就不同广辰;傳遞的是同一個bitmap暇矫,畫的也是相同的。

3.ViewGroup的測量和繪制

  1. 測量: 這個是在面試的時候被坑過了的择吊,如果屬性是wrap_content主要就是調(diào)用OnLayout方法遍歷子View,調(diào)用子View的Measure方法李根;不是則設(shè)定指定值。
    如果是自定義ViewGroup的話几睛,跟View一樣房轿;想用wrap_content屬性就必須重寫OnMeasure方法。

  2. 繪制: 使用dispatchDraw()方法來調(diào)用子View的OnDraw()方法 進(jìn)行繪制所森〈殉郑或者如果設(shè)置了背景顏色或圖片的情況下也會調(diào)用自己的OnDraw()方法

4.自定義View

大家最喜歡的也是最重點內(nèi)容終于來了,初學(xué)時一直覺得感覺很深奧的樣子焕济,其實也很簡單的啦

在View中比較重要的回調(diào)方法:

  • OnfinishInflate:從XML加載組件后回調(diào)
  • OnSizeChanged:組件大小改變時回調(diào)
  • OnMeasure:回調(diào)該方法來進(jìn)行測量
  • OnLayout:回調(diào)該方法確定顯示的位置
  • OnTouchEvent:監(jiān)聽觸摸事件回調(diào)

一般來說可以對現(xiàn)有控件進(jìn)行拓展:例如繼承TextView來實現(xiàn)具有外框的TextView

    @Override
    protected void onDraw(Canvas canvas) {
        //回調(diào)之前實現(xiàn)自己的邏輯纷妆,即在繪制文本之前
        super.onDraw(canvas);
        //回調(diào)之后實現(xiàn)自己的邏輯,即在繪制文本之后
    }

自定義屬性

在res中的values文件夾下晴弃,創(chuàng)建名為attrs的Values resouce File的文件掩幢,<declare-styleable name="TopBar">用來確定是作用于那個View的,下面的attr name就是每個屬性的名稱上鞠。

這里說明一下屬性:

  • diemnsion: 一般為文字大小
  • color:顏色
  • String:字符串
  • reference:引用际邻,一般為background
  • 還可以兩者結(jié)合,用|分隔開
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TopBar">

        <attr name="top_bar_title" format="string"/>
        <attr name="title_text_size" format="dimension"/>
        <attr name="title_text_color" format="color"/>

        <attr name="left_text_color" format="color"/>
        <attr name="left_btn_back" format="reference|color"/>
        <attr name="left_text" format="string"/>

        <attr name="right_text_color" format="color"/>
        <attr name="right_btn_back" format="reference|color"/>
        <attr name="right_text" format="string"/>

    </declare-styleable>

</resources>

何如在代碼中獲取定義好的屬性呢旗国?

private TypedArray mTypedArray;

public void initTypedArray(Context context){
        //指定是從哪讀取的屬性
        mTypedArray = context.obtainStyledAttributes(R.styleable.TopBar);

        mTxtTile = mTypedArray.getString(R.styleable.TopBar_top_bar_title);
        mTxtLeft = mTypedArray.getString(R.styleable.TopBar_left_text);
        mTxtRight = mTypedArray.getString(R.styleable.TopBar_right_text);

        mTitleColor = mTypedArray.getColor(R.styleable.TopBar_title_text_color, Color.WHITE);
        mLeftColor = mTypedArray.getColor(R.styleable.TopBar_left_text_color,Color.WHITE);
        mRightColor = mTypedArray.getColor(R.styleable.TopBar_right_text_color,Color.WHITE);

        mLeftBackground = mTypedArray.getDrawable(R.styleable.TopBar_left_btn_back);
        mRightBackground = mTypedArray.getDrawable(R.styleable.TopBar_right_btn_back);

        mTextSize = mTypedArray.getDimension(R.styleable.TopBar_title_text_size,10);
        //獲取完值后一般都會調(diào)用recycle方法枯怖,來避免重新創(chuàng)建時候的錯誤
        mTypedArray.recycle();

    }

XML中能用嗎注整?怎么用呢能曾?
既然是自定義的控件屬性,當(dāng)然也可以在XML中使用


看到上圖的topbar屬性了嗎肿轨?都是自定義的屬性寿冕,那么怎么才能這樣定義呢?看看Android原本的控件如何定義的吧

這是使用自定義控件的根布局LinearLaout椒袍,看其中的xmlns:android就是系統(tǒng)的驼唱,而我們的就是xmlns:topbarxmlns就是命名空間驹暑;這個名字可以隨意更改玫恳,直接在根布局中改就可以了

組合控件

組合控件一般都是動態(tài)添加子View的辨赐,所以所有控件都是在Java代碼中添加,與xml無關(guān)京办。這時控件大家都會定義掀序,屬性也會設(shè)置如setTextColor();但是Layout的位置呢惭婿?應(yīng)該如下所示:


private ViewGroup.LayoutParams layoutParams;

layoutParams = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.MATCH_PARENT
        );

//然后就可以用addView方法添加到組合控件中了
//例如定義了一個mLeftButton
addView(mLeftButton,layoutParams);

//查看addView源碼
public void addView(View view, ViewGroup.LayoutParams params);

可以看到第四個方法有view和layoutParams的屬性不恭,就選這個

自定義點擊事件接口

一般一個控件都會有點擊事件吧,但是一個組合控件里面有多個控件财饥,這時你就要自己寫一個點擊事件的接口讓別人用了换吧。

public interface OnTopBarClickListener {
        void onLeftClick();
        void onRightClick();
}

定義完后只是空的接口,需要有里面的點擊響應(yīng):

OnTopBarClickListener mListener;

mLeftButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //自行在外定義接口對象
                mListener.onLeftClick();
            }
    });

最后暴露出一個方法讓調(diào)用者注冊接口回調(diào)

public void setOnTopBarClickListener(OnTopBarClickListener listener){
    this.mListener = listener;
}

這樣就ok了,是不是跟用系統(tǒng)的setOnTouchListener一樣用了?就是這么簡單

事件攔截機制簡單分析

首先要知道什么是觸摸事件媒鼓;比如說按下一個按鈕寥殖,按下按鈕是事件一,不小心滑動是時間二噩翠,抬起手是事件三;

Android為觸摸事件封裝了一個類——MotionEvent;基本上所有與事件相關(guān)的操作都需要這個類

  • 事件傳遞dispatchTouchEvent:父控件--->子控件
    返回值:true攔截乖篷;false不攔截

  • 事件處理OnInterceptTouchEvent:子控件--->父控件
    返回值:true處理了;false給上級處理

初始情況下返回值都是false透且。

父控件想攔截事件就給dispatchTouchEvent設(shè)為true撕蔼,這樣就不會傳遞給子控件

子控件不想讓父控件知道自己干了什么,就把OnInterceptTouchEvent置為true秽誊;這樣就不會向父控件報告

是不是非常淺顯易懂鲸沮?這里就不過多闡述,只要知道是這樣的就夠了锅论,深入的以后再說讼溺,或者可以看源碼了解

該筆記源于《Android群英傳》,加之自己的實踐與理解最易,其中話語可能粗糙簡陋怒坯,但可能更通俗直觀,由于每個人知識水平不同藻懒,我只記錄我認(rèn)為不熟的剔猿,重點難點,想看全還請購買醫(yī)生的書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嬉荆,一起剝皮案震驚了整個濱河市归敬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖汪茧,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椅亚,死亡現(xiàn)場離奇詭異,居然都是意外死亡舱污,警方通過查閱死者的電腦和手機什往,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慌闭,“玉大人别威,你說我怎么就攤上這事÷刻蓿” “怎么了省古?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丧失。 經(jīng)常有香客問我豺妓,道長,這世上最難降的妖魔是什么布讹? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任琳拭,我火速辦了婚禮,結(jié)果婚禮上描验,老公的妹妹穿的比我還像新娘白嘁。我一直安慰自己,他們只是感情好膘流,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布絮缅。 她就那樣靜靜地躺著,像睡著了一般呼股。 火紅的嫁衣襯著肌膚如雪耕魄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天彭谁,我揣著相機與錄音吸奴,去河邊找鬼。 笑死缠局,一個胖子當(dāng)著我的面吹牛则奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甩鳄,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼逞度,長吁一口氣:“原來是場噩夢啊……” “哼额划!你這毒婦竟也來了妙啃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揖赴,沒想到半個月后馆匿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡燥滑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年渐北,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铭拧。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赃蛛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搀菩,到底是詐尸還是另有隱情呕臂,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布肪跋,位于F島的核電站歧蒋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏州既。R本人自食惡果不足惜谜洽,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吴叶。 院中可真熱鬧阐虚,春花似錦、人聲如沸蚌卤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽造寝。三九已至磕洪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诫龙,已是汗流浹背析显。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留签赃,地道東北人谷异。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像锦聊,于是被迫代替她去往敵國和親歹嘹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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