1. 程式人生 > >Andrid View事件分發機制原始碼分析

Andrid View事件分發機制原始碼分析

Android 的view樹結構大家都清楚,但是事件序列是經過一個怎樣的處理路徑那。今天就帶著疑問來看看原始碼,去尋找答案。
首先我們先看事件如何從Activity開始分發。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.
OnWindowDismissedCallback, WindowControllerCallback, AutofillManager.AutofillClient { private static final String TAG = "Activity"; private static final boolean DEBUG_LIFECYCLE = false; /** Standard activity result: operation canceled. */ public static final int RESULT_CANCELED =
0; /** Standard activity result: operation succeeded. */ public static final int RESULT_OK = -1; /** Start of user-defined activity results. */ public static final int RESULT_FIRST_USER = 1; ......

我們注意到Activity 實現的介面中,Window.Callback這個。

 
    public interface Callback
{ public boolean dispatchKeyEvent(KeyEvent event); public boolean dispatchKeyShortcutEvent(KeyEvent event); public boolean dispatchTouchEvent(MotionEvent event); public boolean dispatchTrackballEvent(MotionEvent event); public boolean dispatchGenericMotionEvent(MotionEvent event); public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); @Nullable public View onCreatePanelView(int featureId); public boolean onCreatePanelMenu(int featureId, Menu menu); public boolean onPreparePanel(int featureId, View view, Menu menu); public boolean onMenuOpened(int featureId, Menu menu); public boolean onMenuItemSelected(int featureId, MenuItem item); public void onWindowAttributesChanged(WindowManager.LayoutParams attrs); public void onContentChanged(); public void onWindowFocusChanged(boolean hasFocus); public void onAttachedToWindow(); public void onDetachedFromWindow(); public void onPanelClosed(int featureId, Menu menu); public boolean onSearchRequested(); public boolean onSearchRequested(SearchEvent searchEvent); @Nullable public ActionMode onWindowStartingActionMode(ActionMode.Callback callback); @Nullable public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type); public void onActionModeStarted(ActionMode mode); public void onActionModeFinished(ActionMode mode); default public void onProvideKeyboardShortcuts( List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { }; default public void onPointerCaptureChanged(boolean hasCapture) { }; }

Callback是window的內部介面類。可以看出方法還是特別多,我們只關注
public boolean dispatchTouchEvent(MotionEvent event);

下面來看看Activity如何實現這個方法

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

首先判斷如果是 ACTION_DOWN事件,就呼叫一個onUserInteraction方法,這個方法是需要APP程式設計師自己覆寫的,完成自己的業務需求。

接下來是getWindow().superDispatchTouchEvent(ev)。這個方法如果返回true,即消費了事件,那不會呼叫後面activity自己的onTouchEvent(ev)。activity自己怎麼消費這個事件那?

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

看到mWindow.shouldCloseOnTouch(this, event)如果返回true,activity就會主動finish。。。

    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

我們看下邏輯,哪個混蛋在外部設定了mCloseOnTouchOutside為true,同時又是一個ACTION_DOWN事件,點選位置又在窗體外,decorView不為空,activity就要finish了。 是不是感覺有點意思,我們都記得對話方塊有這個屬性,點選對話方塊外部,對話方塊消失, activity也可以表現成對話方塊的樣式,功能也是一樣的。

    private boolean isOutOfBounds(Context context, MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
        final View decorView = getDecorView();
        return (x < -slop) || (y < -slop)
                || (x > (decorView.getWidth()+slop))
                || (y > (decorView.getHeight()+slop));
    }

ViewConfiguration.get(context).getScaledWindowTouchSlop()這個程式碼跟蹤一下



 /**
     * Distance in dips a touch needs to be outside of a window's bounds for it to
     * count as outside for purposes of dismissing the window.
     */
    private static final int WINDOW_TOUCH_SLOP = 16;



  private ViewConfiguration(Context context) {
        final Resources res = context.getResources();
        final DisplayMetrics metrics = res.getDisplayMetrics();
        final Configuration config = res.getConfiguration();
        final float density = metrics.density;
        final float sizeAndDensity;
        if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) {
            sizeAndDensity = density * 1.5f;
        } else {
            sizeAndDensity = density;
        }

      .......
        mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);

.....

  /**
     * @return Distance in pixels a touch must be outside the bounds of a window for it
     * to be counted as outside the window for purposes of dismissing that window.
     */
    public int getScaledWindowTouchSlop() {
        return mWindowTouchSlop;
    }

slop其實是一個根據density寫死的一個敏感度的數值。

繼續回到主線-------------------》getWindow().superDispatchTouchEvent(ev)
getWindow是內部一個成員變數,它怎麼來的那

 private Window mWindow;

。。。。。
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
    。。。。
    }
 

mWindow其實是外部呼叫Activity.attach方法時候建立的,我們可以看到它其實是一個PhoneWindow例項,我們注意到PhoneWindow建構函式的入參有個window ,猜測一個PhoneWindow 繼承於window,內部的方法又委託一個真正的window例項。應該是靜態代理模式。



public class PhoneWindow extends Window implements MenuBuilder.Callback {

    public PhoneWindow(Context context, Window preservedWindow,
            ActivityConfigCallback activityConfigCallback) {
        this(context);
   
        mUseDecorContext = true;
        if (preservedWindow != null) {
            mDecor = (DecorView) preservedWindow.getDecorView();
            mElevation = preservedWindow.getElevation();
            mLoadElevation = false;
            mForceDecorInstall = true;
            getAttributes().token = preservedWindow.getAttributes().token;
        }
    。。。。
    }

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

轉了一圈事件來到了PhoneWindow 內部的 mDecor(DecorView).那這個DecorView是啥那。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

DecorView其實是一個FrameLayout ,那它跟我們的佈局又是什麼關係那。

    @Override
    public void setContentView(int layoutResID) {
 
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

PhoneWindow中有個setContentView(int layoutResID)方法,是不是倍感親切。對!這個就是我們寫一個activity要呼叫的方法,把自己寫的XML佈局設定進去。一般會走到這個方法mLayoutInflater.inflate(layoutResID, mContentParent);

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }



  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

又遇到老朋友inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) ,走題走題了,裡面的東西以後再分析。就是XML解析,view一層層add進去,最後加到mContentParent ,那mContentParent是啥,肯定和decor view有關係,因為

if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
。。。



 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
  // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
        。。。。
    }

我們看到mContentParent = generateLayout(mDecor);

    protected ViewGroup generateLayout(DecorView decor) {
 
 
......
  mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

 ..........
        return contentParent;
    }


 /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

我們在忽略了N多行程式碼後找到了這個關鍵的部分,我們的寫的XML的父節點叫content,是個FrameLayout ,這個才是大家常說的一個優化,如果你的xml根節點是FrameLayout,那可以用. merge 有什麼使用限制那,我們找找

 if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);



---------------------------------------

  void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent
            
           

相關推薦

Andrid View事件分發機制原始碼分析

Android 的view樹結構大家都清楚,但是事件序列是經過一個怎樣的處理路徑那。今天就帶著疑問來看看原始碼,去尋找答案。 首先我們先看事件如何從Activity開始分發。 public class Activity extends ContextThemeWrapper

Android中ViewGroup、View事件分發機制原始碼分析總結(雷驚風)

1.概述         很長時間沒有回想Android中的事件分發機制了,開啟目前的原始碼發現與兩三年前的實現程式碼已經不一樣了,5.0以後發生了變化,更加複雜了,但是萬變不離其宗,實現原理還是一樣的,在這裡將5.0以前的時間分發機制做一下原始碼剖析及總結。會涉及到幾個方

Android事件分發機制原始碼分析之Activity篇

在之前的事件分發分析中,曾提及到View的事件是由ViewGroup分發的,然而ViewGroup的事件我們只是稍微帶過是由Activity分發的。而我們知道,事件產生於使用者按下螢幕的一瞬間,事件生成後,經過一系列的過程來到我們的Activity層,那麼事件是怎樣從Activity傳遞

Android事件分發機制原始碼分析下----ViewGroup事件分發分析

ViewGroup事件分發機制         上篇文章從原始碼的角度對View的事件分發進行了分析,這篇文章繼續對事件分發進行介紹,從原始碼的角度分析ViewGroup的事件分發,從繼承關係看ViewGroup也屬於View的一種,但它的內部可以放置View,簡單的結論我

Android View 事件分發機制原始碼詳解(ViewGroup篇)

前言 我們在學習View的時候,不可避免會遇到事件的分發,而往往遇到的很多滑動衝突的問題都是由於處理事件分發時不恰當所造成的。因此,深入瞭解View事件分發機制的原理,對於我們來說是很有必要的。由於View事件分發機制是一個比較複雜的機制,因此筆者將寫成兩篇文

Android View 事件分發機制 原始碼解析(ViewGroup篇)

1. 前言 android點選 事件一直以來都是很多安卓程式設計師的心病,之前通過demo模擬總結出一些經驗,但是不看原始碼的程式設計師不是好程式設計師,這段時間,系統的梳理了下整個事件傳遞的原始碼,希望可以幫助大家徹底理解andriod的點選

【Android】原始碼分析 - View事件分發機制

事件分發物件 (1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。 (2)事件型別分為 ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_POINTER_D

詳細解析Android的View事件分發機制 附帶原始碼分析

前言 在Android中,事件分發機制是一塊很重要的知識點,掌握這個機制能幫你在平時的開發中解決掉很多的View事件衝突問題,這個問題也是面試中問的比較多的一個問題了,今天就來總結下這個知識點。 事件分發機制 事件分發原因 Android中頁面上的View是以

自定義View(二)View事件分發機制原始碼解析

View的事件分發機制是Android中的一個難點,也是非常重要的知識點,充分理解和掌握事件分發機制有助於我們在自定義view的過程中更好地設計和解決事件相關問題。下面我們通過原始碼的角度去分析一下Android是怎麼處理view事件的。 一個事件(比如手指按下螢幕的down事件)首先傳遞到

View事件分發機制分析

View 事件分發是很重要的知識點,只有理解其中的原理 在寫程式碼過程中更精準的處理程式碼邏輯,控制好 api 的呼叫時機。本文通過閱讀SDK 28的原始碼,在這裡做一次輸出,深入理解下。 目錄 一、例項引申 二、事件分發原理 Activity

Android View事件分發機制

作為程式猿,最不想 看的但是也不得不去看的就是原始碼!所謂知其然也要知其所以然,神祕的大佬曾經說過進階的方法就是READ THE FUCKING CODE! 認識MotionEvent 負責集中處理所有型別裝置的輸入事件.我們對螢幕的點選,滑動,擡起等一系的

Android中View事件分發機制

View事件分發機制 今天要寫一寫Android中比較重要的一個核心,View事件分發機制。那麼事件分發機制是什麼,為什麼要寫這個呢, 下面將一一講解出來。 前言 相信大家對Android基礎知識都已經有所瞭解啦,因為畢竟Android已經涼了,應該也沒有多少新

Android VSync事件分發過程原始碼分析

在上一篇文章Android VSync訊號產生過程原始碼分析中分別介紹了VSync的兩種產生方式,無論是通過硬體中斷產生還是通過軟體模擬產生,VSync事件最終都會交給EventThread執行緒來分發給所有VSync事件接收者。VSync事件接收者有很多,Surface

android SDK-25事件分發機制--原始碼正確解析

android SDK-25事件分發機制–原始碼正確解析 Android 事件分發分為View和ViewGroup的事件分發,ViewGroup比View過一個攔截判斷,viewgroup可以攔截事件,從而決定要不要把事件傳遞給子view,因為view沒

view事件分發機制

參考:(http://blog.csdn.net/lmj623565791/article/details/38960443) 【總結】以button為例, 1、點選button(略微滑動一下)的過程中依次執行: View.dispatchEven()——

一文讀懂Android View事件分發機制

Android View 雖然不是四大元件,但其並不比四大元件的地位低。而View的核心知識點事件分發機制則是不少剛入門同學的攔路虎。ScrollView巢狀RecyclerView(或者ListView)的滑動衝突這種老大難的問題的理論基礎就是事件分發機制。 事件

【朝花夕拾】Android自定義View篇之(六)Android事件分發機制(中)從原始碼分析事件分發邏輯及經常遇到的一些“詭異”現象

前言        轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝!        在上一篇文章【【朝花夕拾】Android自定義View篇之(

Android 原始碼解析View的touch事件分發機制

概述 本篇主要分析的是touch事件的分發機制,網上關於這個知識點的分析文章非常多。但是還是想通過結合自身的總結,來加深自己的理解。對於事件分發機制,我將使用兩篇文章對其進行分析,一篇是針對View的事件分發機制解析,一篇是針對ViewGroup的事件分發機制

Android筆記-從ViewGroup的dispatchTouchEvent原始碼分析事件分發機制

前一篇文章:淺析了事件攔截機制 主要是從demo中看的現象總結的結論 文中涉及到以下方法 1. ViewGroup的三個方法: dispatchTouchEvent:事件分發 onInterceptTouchEvent:事件攔截 onTouchEve

事件分發機制的詳解及原始碼分析

事件分發機制詳解 MotionEvent 主要分為以下幾個事件型別: ACTION_DOWN 手指開始觸控到螢幕的那一刻響應的是DOWN事件 ACTION_MOVE 接著手指在螢幕上移動響應的是MOVE事件 ACTION_UP 手指從螢幕上鬆開的那一刻響