編輯:關於Android編程
View在Android的地位堪比四大組件,Android為我們提供了很多的系統控件。但是為了區別一般性,我們往往需要自定義View,這就要求我們對View的事件體系和工作原理有深入的理解,只有這樣才能做出完美的自定義控件
onTouch->onTouchEvent->onClick
當一個View需要處理事件時,如果它設置了OnTouchListener,那麼OnTouchListener的onTouch方法會被回調。 這時事件如何處理還得看onTouch的返回值,如果返回false,則當前View的onTouchEvent方法會被調用;如果返回true,那麼onTouchEvent方法將不會被調用。由此可見,給View設置的onTouchListener,其優先級比onTouchEvent要高。 如果當前方法中設置了onClickListener,那麼它的onClick方法會被調用。可以看出,常用的OnClickListener,其優先級別最低。MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams(); params.width += 10; params.height += 10; mButton.requestLayout(); //獲知 mButton.setLayoutParams(params);
- **三種方法的使用對比**
- scrollTo/scrollBy:操作簡單,適合對View內容的滑動;
- 動畫:操作簡單,主要適合於沒有交互的View和實現復雜的動畫效果;
- 改變布局參數:操作稍微復雜,適用於有交互的View。
### View的事件分發機制
事件的分發機制由三個重要方法來共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
用來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的DispatchTouchEvent方法的影響,表示是否消耗當前事件。 事件攔截:public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法內部調用,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列當中,此方法不會被再次調用,返回結果表示是否攔截當前事件。 事件響應:public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。 三者的關系可以總結為如下偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
- 事件傳遞機制的11個結論:
1. 同一個事件序列是從手指觸摸屏幕的那一刻起,到手指離開屏幕那一刻結束,這個過程中所產生的一系列事件。這個事件序列以down事件開始,中間含有數量不定的move事件,最終以up事件結束。
2. 一個事件序列只能被一個View攔截且消耗,不過通過事件代理TouchDelegate,可以將onTouchEvent強行傳遞給其他View處理。
3. 某個View一旦決定攔截,那麼這一事件序列就都只能由它來處理
4. 某個View一旦開始處理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼事件會重新交給它的父元素處理,即父元素的onTouchEvent會被調用。
5. 如果View不消耗除ACTION_DOWN以外的事件,那麼這個點擊事件會消失,此時父元素的onTouchEvent並不會調用,並且當前View可以持續收到後續的事件,最終這些消失的事件會傳遞到Activity。
6. ViewGroup默認不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法默認返回false。
7. View沒有onIntercepteTouchEvent方法,一旦有點擊事件傳遞給它,那麼它的onTouchEvent方法就會被調用。
8. View的onTouchEvent默認都不會消耗事件(返回false),除非它是可點擊的(clickable和longClickable有一個為true)。View的longClickable默認都為false,clickable要分情況看,比如Button默認為true,TextView默認為false。
9. View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態,只要它的clickable或者longClickable有一個為true,那麼它的onTouchEvent就返回true。
10. onClick會發生的前提是當前View是可點擊的,並且它受到down和up的事件。
11. 事件傳遞是由外向內的,即事件總是先傳遞給父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法就可以在子元素中干擾父元素的事件分發過程,但ACTION_DOWN事件除外。
View的繪制流程
三個過程
measure:測量View的寬和高 layout:確定View在父控件中的放置位置 draw:負責將View繪制在屏幕上。
幾個常用回調方法
構造方法 onAttachToWindow:在包含View的Activity啟動時調用 onDetachFromWindow:在包含View的Activity退出或者View被remove時回調 onVisibilityChanged:當View的可見狀態發生改變時調用
兩個重要概念
ViewRoot:連接WindowManager(外界訪問Window的入口)和DecorView(頂級View)的紐帶,View的三大流程均是通過ViewRoot來完成的。 DecorView:頂級View
View的繪制流程
View的繪制流程是從ViewRoot的PerformTraversals方法開始的。
如上圖所示:
performTraversals會依次調用performMeasure, performLayout, performDraw三個方法,這三個方法分別完成頂層View的measure,layout,draw方法,onMeasure又會調用所有子元素的measure過程,直到完成整個View樹的遍歷。同理,performLayout, performDraw的傳遞流程與performMeasure相似。唯一不同在於,performDraw的傳遞過程在draw方法中通過dispatchDraw實現,但沒有本質區別。 Measure過程後可以調用getMeasureWidth和getMeasureHeight方法獲取View測量後的寬高,與getWidth和getHeight的區別是:getMeasuredHeight()返回的是原始測量高度,與屏幕無關,getHeight()返回的是在屏幕上顯示的高度。實際上在當屏幕可以包裹內容的時候,他們的值是相等的,只有當view超出屏幕後,才能看出他們的區別。當超出屏幕後,getMeasuredHeight()等於getHeight()加上屏幕之外沒有顯示的高度。 Layout過程確定View四個頂點的位置和實際的寬高。 Draw過程確定View的顯示,只有draw方法完成後View的內容才會出現在屏幕上。
MeasureSpec的使用
measureSpec的作用:很大程度上決定了一個View的尺寸規格
下面是它的一些常量和方法:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 精確模式,對應LayoutParams中的match_parent和具體數值這兩種模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 最大模式,大小不定,但是不能超過窗口的大小
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec和LayoutParams的關系
View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams決定。
View的masure過程由ViewGroup傳遞,具體觀察ViewGroup的measureChildWithMargins方法
DecorView的MeasureSpec由窗口尺寸和自身的LayoutParams決定。
子元素的MeasureSpec還和View的margin和padding有關。
具體情況如下圖:
### 如何讓自定義View支持自定義屬性
在values目錄下創建自定義屬性的XML,比如attrs.xml,format負責定義屬性的格式,可以是“color”代表顏色,也可以是reference代表資源id,dimension代表尺寸。
2. 在View的構造函數中解析自定義屬性的值並做相應處理,解析circle_color這個屬性的值:
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
3. 在布局文件中使用自定義屬性,使用前需要在布局文件中添加schemas生命:`xmlns:app="http://schemas.android.com/apk/res-auto"`在這個聲明中app是自定義屬性的前綴,也可以換成其他名字,不過要與CircleView中的自定義屬性一致。
重寫View應該注意哪些方面
自定義View的分類大致可以分為4類:
繼承View重寫onDraw方法 繼承ViewGroup派生特殊的Layout 繼承特定的View(比如TextView)
繼承特定的ViewGroup(比如LinearLayout)
自定義View須知:
讓View支持wrap_content 如果有必要,讓View支持padding 盡量不要在View中使用Handler,沒必要,有post系列方法 View中如果有線程或動畫,需要及時停止,參考View#onDetachedFromWindow View帶有滑動嵌套情形時,需要處理好滑動沖突
實用范圍
注意事項
繼承View重寫onDraw方法
不規則效果
繼承ViewGroup派生特殊的Layou
自定義布局
繼承特定的View(比如TextView)
擴展已有的View的功能
繼承特定的ViewGroup(比如LinearLayout)
自定義布局
1、switch(開關)mui提供了開關控件,點擊滑動兩種手勢都可以對開關控件進行操作,UI如下:默認開關控件,帶on/off文字提示,打開時為綠色背景,基本class類
一、前言本文主要來介紹一個實際案例就是如何通過這個框架來修改系統的地理位置信息來實現隱藏功能,在如今社交工具的發展特別是微信,他有一個實時位置共享功能,那麼對於那些不是單
1. Sax概述SAX是一種占用內存少且解析速度快的解析器,它采用的是事件啟動,不需要解析完整個文檔,而是按照內容順序看文檔某個部分是否符合xml語法,如果符合就觸發相應
TelephonyManager是一個管理手機通話狀態、電話網絡信息的服務類,該類提供了大量的getXxx(),方法獲取電話網絡的相關信息。 TelephonyManag