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的測量和繪制
測量: 這個是在面試的時候被坑過了的择吊,如果屬性是
wrap_content
主要就是調(diào)用OnLayout
方法遍歷子View,調(diào)用子View的Measure
方法李根;不是則設(shè)定指定值。
如果是自定義ViewGroup的話几睛,跟View一樣房轿;想用wrap_content
屬性就必須重寫OnMeasure
方法。繪制: 使用
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:topbar
;xmlns
就是命名空間驹暑;這個名字可以隨意更改玫恳,直接在根布局中改就可以了
組合控件
組合控件一般都是動態(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
秽誊;這樣就不會向父控件報告
是不是非常淺顯易懂鲸沮?這里就不過多闡述,只要知道是這樣的就夠了锅论,深入的以后再說讼溺,或者可以看源碼了解