《Android群英傳》學習筆記之Android控制元件架構與自定義控制元件詳解
阿新 • • 發佈:2018-12-13
一、Android控制元件架構:
-
控制元件大致分為兩類:ViewGroup控制元件與View控制元件。View是繪製在螢幕上的使用者能與之互動的一個物件。而ViewGroup則是一個用於存放其他View(和ViewGroup)物件的佈局容器。其中,View是所有UI元件的基類,而 ViewGroup是容納這些元件的容器,其本身也是從View派生出來的。
- (1)View:View物件是Android平臺中使用者介面體現的基礎單位,它是用來建立互動性的UI元件(如:按鈕文字框等等)的widgets的父類。它們提供了諸如文字輸入框和按鈕之類的UI物件的完整實。
- (2)ViewGroup:ViewGroup繼承自View,是一種特殊的View,它可以裝其他的Views(或其他的ViewGroup)。ViewGroup是佈局(layouts)和views containers的父類。它的直接子類有: FrameLayout、GridLayout、LinearLayout等等。
- 下面列出View及ViewGroup的所有子類
- View派生出的直接子類有 ImageView,ProgressBar,TextView,ViewGroup,AnalogClock,KeyboardView,ViewStub,SurfaceView;
- View派生出的間接子類有 Button,CheckBox,AbsoluteLayout,AdapterView,AdapterViewAnimator,AdapterViewFlipper,AppWidgetHostView,AutoCompleteTextView,CalendarView,CheckedTextView,Chronometer,AbsListView,AbsSeekBar,AbsSpinner,CompoundButton;
- ViewGroup派生出的直接子類有 AbsoluteLayout,FrameLayout,LinearLayout,RelativeLayout,AdapterView,FragmentBreadCrumbs,SlidingDrawer;
- ViewGroup派生出的間接子類有 ListView,GridView,AbsListView,AbsSpinner,AdapterViewAnimator,AdapterViewFlipper,AppWidgetHostView,CalendarView,DatePicker,DialerFilter,ExpandableListView,Gallery,GestureOverlayView,HorizontalScrollView,ImageSwitcher;
-
View樹結構 (eg:通常在Activty中使用的findViewById()方法就是在控制元件樹中以樹的深度優先遍歷來查詢對應元素)
-
UI介面架構圖(DecorView被設定為整個應用視窗的根View)
-
標準檢視樹
-
在程式碼中,當程式在onCreate()方法中呼叫setContentView()方法後,ActivityManagerService會回撥OnResume()方法,此時系統會把整個DecorView新增到PhoneWindow中,並讓其顯示出來
二、View/ViewGroup的測量與繪製
1、View的測量
- 在onMeasure() 方法中測量,藉助MeasureSpec類來幫助測量View
- 測量模式:
- EXACTLY(精確值模式):指定數值(在onMeasure()方法中,如果不重寫方法,只能使用這種測量模式)
- AT_MOST(最大值模式):例如wrap_content,只要不超過父控制元件允許的最大尺寸即可
- UNSPECIFIED(不指定大小):通常在繪製自定義View時使用
- 當View需要使用wrap_content屬性時,重寫onMeasure()方法,最終要做的工作就是把測量後的寬高作為引數設定給setMeasuredDimension(寬,高) 方法
- 自定義測量值
- 1.從MeasureSpec物件中提取出具體的測量模式和大小
int specMode = MeasureSpec.getMode(measureSpec) int specSize = MeasureSpec.getSize(measureSpec)
- 2.通過測量的模式給出不同的測量值
- 1.從MeasureSpec物件中提取出具體的測量模式和大小
2、View的繪製
- 系統2D繪圖API需使用Canvas物件繪製,Canvas像畫板,使用Paint就可以在上面作畫。
- 通常需要繼承View並重寫的它的onDraw() 方法進行繪圖
- 要在建立Canvas物件的時候同時傳入一個bitmap物件。因為傳進去的bitmap與通過這個bitmap建立的Canvas畫布是緊緊聯絡在一起的,這個過程稱之為裝載畫布。這個bitmap用來儲存所有繪製在Canvas上的畫素資訊。
Canvas canvas = new Canvas(bitmap); //如果在onDraw()方法中,可將bitmap1再裝載到其他Canvas物件中,重新整理後會重新裝載畫布 canvas.drawBitmap(bitmap1,0,0,null);
3、ViewGroup的測量:
- ViewGroup在測量時通過遍歷所有子View,從而呼叫子View的Measure方法來獲得每一個子View的測量結果,前面所說的對View的測量,就是在這裡進行的。
4、ViewGroup的繪製:
- ViewGroup如果不是被指定了背景色,通常不需要繪製。但是ViewGroup會使用dispatchDraw() 方法遍歷所有的子View,並通過呼叫子View來完成繪製工作。
三、自定義View
通常情況下,有以下三種方法來實現自定義控制元件
- 對現有控制元件進行拓展
- 通過組合來實現新的控制元件
- 重寫View來實現全新的控制元件
1、對現有控制元件進行拓展
- 一般來說,在onDraw() 方法中進行對原生控制元件的拓展
@Override protected void onDraw(Canvas canvas) { //在回撥父類方法前,實現自己的邏輯,對TextView來說,即是在繪製文字內容前 super.onDraw(); //在回撥父類方法後,實現自己的邏輯,對TextView來說,即是在繪製文字內容後 }
- 程式呼叫super.onDraw(canvas) 方法來實現原生控制元件的功能。
- 通過canvas、paint等物件實現邏輯
- 一般在構造方法中完成物件的初始化工作,如初始化畫筆
mPaint1 = new Paint(); mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); mPaint1.setStyle(Paint.Style.FILL);
- 可通過getPaint() 方法獲取到當前繪製原生控制元件的Paint物件,然後對這個物件設定一些原生控制元件沒有的屬性
2、建立複合控制元件
- 這種方式通常需要繼承一個合適的ViewGroup,再給他新增指定功能的控制元件,從而組合成新的複合控制元件
Ⅰ、定義屬性
①確定屬性:在res資源目錄的values目錄下建立一個attrs.xml的屬性定義檔案
<resources>
<declare-styleable name="TopBar">
<attr name = "title" format = "string" />
<attr name = "titeTextSize" format = "dimension" />
<attr name = "titleTextColor" format = "color" />
<attr name = "rightBackground" format = "reference|color"
......
</declare-styleable>
</resources>
- 通過 標籤聲明瞭使用自定義屬性,並通過name屬性來確定引用的名稱
- 通過 標籤來宣告具體的自定義屬性,並通過format屬性來指定屬性的型別
- 需要注意的是,有的屬性可以是顏色屬性,也可以是引用屬性。比如按鈕的背景,可以指定為具體的顏色,也可以把它指定為一張具體的圖片,所以使用“|”來分隔不同的屬性
②獲取屬性
- 系統提供了TypedArray這樣的資料結構來獲取自定義屬性集,然後通過TypedArray物件的getString()、getColor() 等方法,就可以獲取這些定義的屬性值。當獲取完所有屬性值後,需要呼叫TypedArray的recycle方法來完成資源的回收。
//通過這個方法,將在attrs.xml中定義的declare-styleable的所有屬性的值儲存到TypedArray中。
//第一個引數為xml名,第二個引數為style的name
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.TopBar);
//從TypedArray中取出對應的值來為要設定的屬性賦值
mLeftTextColor = ta.getColor(R.styleable.TopBar_leftText,0);
mLeftText = ta.getString(R.styleable.TopBar_leftText);
mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftText);
//獲取玩TypedArray的值後,一般要呼叫recyle的方法來避免重新建立時的錯誤
ta.recycle();
Ⅱ、組合控制元件
- 通過動態新增控制元件的方式,使用addView() 的方法將重置屬性後的系統控制元件放入自定義的模板中
//為元件元素設定相應的佈局元素
mLeftParams = new LayoutParams(LayoutParamas.WRAP_CONTENT, LayoutParamas.MATCH_PARENT);
mLeftParamas.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
//新增到ViewGroup
addView(mLeftButton, mLeftParams);
- 實現自定義控制元件的點選事件
①定義介面
//介面物件,實現回撥機制,在回撥方法中,通過對映的介面物件呼叫介面中的方法,而不用去考慮如何具體實現
public interface topbarClickListener {
//左按點選事件
void leftClick();
//右按點選事件
void rightClick();
}
②暴露介面給呼叫者(使用者)
//按鈕的點選事件,不需要具體的實現
//秩序呼叫介面的方法,回撥的時候,會有具體的實現
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
//暴露一個方法給呼叫者來註冊介面回撥
//通過介面來獲得回撥者對介面方法的實現
public void setOnTopbarClickListener(topbarClickListener mListener) {
this.mListener = mListener;
}
③實現介面回撥(定義具體實現者) 在呼叫者的程式碼中,呼叫者需要實現這樣一個介面,並完成介面中的方法,確定具體實現邏輯,並使用第二步中暴露的方法,將介面的物件傳遞進去,從而完成回撥。
mTopbar.setOnTopbarClickListener(
new TopBar.topbarClickListener(){
@Override
public void rightClick(){
Toast.makeText(TopBarTest.this, "right", Toast.LENGTH_SHORT).show();
}
@Override
public void leftClick(){
Toast.makeText(TopBarTest.this, "left", Toast.LENGTH_SHORT).show();
}
}
)
Ⅲ、引用UI模板
- 在引用前,需要指定引用第三方控制元件的名稱空間。
//假設將引入的第三方控制元件的名字空間取名為custom xmlns:custom="http://schemas.android.com/apk/res-auto"
- 使用自定義View與系統原生的View 的最大區別就是在申明控制元件時,需要指定完整的包名,而在引用自定義的屬性時,需要使用自定義的xmlns名字。
- 通過include引用佈局檔案模板
<include layout="@layout/topbar" />
3、重寫View來實現全新的控制元件
- 建立一個自定義View,難點在於繪製控制元件和實現互動
- 通常需要繼承View類,並重寫它的onDraw()、onMeasure()等方法來實現繪製邏輯。
- 同時通過onTouchEvent()等觸控事件來實現互動邏輯。
四、自定義ViewGroup
- ViewGroup存在的目的就是為了對其子View進行管理,為其子View新增顯示、響應的規則。
- 通常需要重寫onMeasure() 方法對子View進行測量
- 重寫onLayout() 方法來確定子View的位置
- 重寫onTouchEvent() 方法來增加響應事件
五、事件攔截機制分析
- Android為觸控事件封裝了一個類:MotionEvent
- 觸控事件先按檢視樹的順序從上到下順序對事件進行分發、攔截,再按逆序進行事件處理或稽核。
- 事件傳遞的返回值:True,攔截,不繼續;false,不攔截,繼續流程。
- 事件處理的返回值:True,處理了,不用稽核了;false,給上級處理
- ViewGroup與View的方法:
- ViewGroup
//事件分發 public boolean dispatchTouchEvent(MotionEvent ev) //事件攔截 public boolean onInterceptTouchEvent(MotionEvent ev) //事件稽核 public boolean onTouchEvent(MotionEvent ev)
- View(位於最底層,無事件攔截的方法)
//事件分發 public boolean dispatchTouchEvent(MotionEvent ev) //事件處理 public boolean onTouchEvent(MotionEvent ev)
- ViewGroup