Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android面試題(三)——View的事件體系和工作原理

Android面試題(三)——View的事件體系和工作原理

編輯:關於Android編程

引言


View在Android的地位堪比四大組件,Android為我們提供了很多的系統控件。但是為了區別一般性,我們往往需要自定義View,這就要求我們對View的事件體系和工作原理有深入的理解,只有這樣才能做出完美的自定義控件

面試題


View中onTouch,onTouchEvent和onClick的執行順序

onTouch->onTouchEvent->onClick

當一個View需要處理事件時,如果它設置了OnTouchListener,那麼OnTouchListener的onTouch方法會被回調。 這時事件如何處理還得看onTouch的返回值,如果返回false,則當前View的onTouchEvent方法會被調用;如果返回true,那麼onTouchEvent方法將不會被調用。由此可見,給View設置的onTouchListener,其優先級比onTouchEvent要高。 如果當前方法中設置了onClickListener,那麼它的onClick方法會被調用。可以看出,常用的OnClickListener,其優先級別最低。

View的滑動方式

三種方式:
a. 通過View本身提供的scrollTo/scrollBy方法
移動的是View的內容,View本身不移動
b. 通過動畫給View施加平移效果實現滑動
移動的View的影像,View本身位置不發生改變。Android3.0以下,移動後的View點擊事件發生的位置不會改變
c. 通過改變View的LayoutParams使View重新布局實現滑動
改變布局參數,代碼如下:
MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
params.width += 10;
params.height += 10;
mButton.requestLayout();
//獲知 mButton.setLayoutParams(params);
- **三種方法的使用對比**
  - scrollTo/scrollBy:操作簡單,適合對View內容的滑動;
  - 動畫:操作簡單,主要適合於沒有交互的View和實現復雜的動畫效果;
  - 改變布局參數:操作稍微復雜,適用於有交互的View。
### View的事件分發機制
事件的分發機制由三個重要方法來共同完成:dispatchTouchEventonInterceptTouchEventonTouchEvent
事件分發: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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved