前言
“當(dāng)Dialog彈出來(lái)時(shí)工禾,Dialog下面的控件能否點(diǎn)擊运提?”
“當(dāng)然是不能點(diǎn)擊啊”
“那么為什么不能點(diǎn)擊呢?”
“額闻葵,emmmm”
相信不少人對(duì)于Dialog使用駕輕就熟了民泵,對(duì)于Dialog下面的控件能否點(diǎn)擊也是非常清楚,但是當(dāng)被問(wèn)之原理時(shí)槽畔,可能有部分童鞋就答不出來(lái)了栈妆。那么這里我們就來(lái)從源碼層面上面講解下這個(gè)原因吧。
首先我們先來(lái)看看Dialog是怎么使用的:
Dialog dialog = new Dialog(TestActivity.this);
dialog.setContentView(R.layout.dialog_test);
dialog.show();
那么我們看到有三步厢钧,我們一步一步講
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == ResourceId.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//新建PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//將回調(diào)注入到window當(dāng)中
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
從代碼上面可以看出鳞尔,這里是新建了一個(gè)PhoneWindow,Dialog和View之間都是通過(guò)Window來(lái)交互早直,這一點(diǎn)跟Activity和View之間的關(guān)系也是類似寥假。
/**
* Set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
* @param layoutResID Resource ID to be inflated.
*/
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
代碼就一行,這里我們可以看到Dialog其實(shí)把主要處理邏輯也都交給了window霞扬,這里面的window對(duì)象其實(shí)就是PhoneWindow糕韧,所以我們?nèi)タ聪翽honeWindow里面的操作:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//初始化decorView
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;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
if (mTransitionManager == null) {
final int transitionRes = getWindowStyle().getResourceId(
R.styleable.Window_windowContentTransitionManager,
0);
if (transitionRes != 0) {
final TransitionInflater inflater = TransitionInflater.from(getContext());
mTransitionManager = inflater.inflateTransitionManager(transitionRes,
mContentParent);
} else {
mTransitionManager = new TransitionManager();
}
}
mEnterTransition = getTransition(mEnterTransition, null,
R.styleable.Window_windowEnterTransition);
mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReturnTransition);
mExitTransition = getTransition(mExitTransition, null,
R.styleable.Window_windowExitTransition);
mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReenterTransition);
mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
R.styleable.Window_windowSharedElementEnterTransition);
mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReturnTransition);
mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
R.styleable.Window_windowSharedElementExitTransition);
mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReenterTransition);
if (mAllowEnterTransitionOverlap == null) {
mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowEnterTransitionOverlap, true);
}
if (mAllowReturnTransitionOverlap == null) {
mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowReturnTransitionOverlap, true);
}
if (mBackgroundFadeDurationMillis < 0) {
mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
R.styleable.Window_windowTransitionBackgroundFadeDuration,
DEFAULT_BACKGROUND_FADE_DURATION_MS);
}
if (mSharedElementsUseOverlay == null) {
mSharedElementsUseOverlay = getWindowStyle().getBoolean(
R.styleable.Window_windowSharedElementsUseOverlay, true);
}
}
}
}
重點(diǎn)關(guān)注installDecor()。window都會(huì)先初始化一個(gè)DecorView喻圃,然后把我們setContentView進(jìn)來(lái)的View給添加到這個(gè)對(duì)應(yīng)的DecorView里面去(Activity里面的Window操作亦是如此)萤彩。所以這里先初始化了一個(gè)DecorView。然后將window注入到decorView當(dāng)中去斧拍。
/**
* Start the dialog and display it on screen. The window is placed in the
* application layer and opaque. Note that you should not override this
* method to do initialization when the dialog is shown, instead implement
* that in {@link #onStart}.
*/
public void show() {
...代碼省略...
mWindowManager.addView(mDecor, l);
...代碼省略
}
這里通過(guò)windowManager將decorView添加到window上面去雀扶,此處WindowManager是WindowManagerImpl,然后又執(zhí)行WindowManagerGlobal的addView方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...代碼省略...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
關(guān)鍵一步在這里饮焦,我們可以看到new了一個(gè)ViewRootImpl的對(duì)象怕吴,并且將DecorView注入進(jìn)去,賦值給mView县踢。
通過(guò)原來(lái)Android觸控機(jī)制竟是這樣的?這篇文章伟件,這才找到了源頭硼啤,所有的點(diǎn)擊事件通過(guò)硬件設(shè)備檢測(cè),經(jīng)過(guò)底層會(huì)調(diào)用到ViewRootImpl的ViewPostImeInputStage里面來(lái)斧账。但是因?yàn)閂iewRootImpl存在著多個(gè)谴返,到底是哪個(gè)會(huì)接收到回調(diào)呢煞肾,十分鐘了解Android觸摸事件原理(InputManagerService)告訴我們會(huì)根據(jù)Z軸的高度,獲取最近一個(gè)窗口嗓袱,然后執(zhí)行對(duì)應(yīng)ViewRootImpl里面ViewPostImeInputStage的監(jiān)聽方法->執(zhí)行mView.dispatchPointerEvent(event)籍救。
此處的mView便是Dialog所在的DecorView,然后才開始由decorView進(jìn)行事件分發(fā),我們先看View的dispatchPointerEvent方法:
/**
* Dispatch a pointer event.
* <p>
* Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
* other events to {@link #onGenericMotionEvent(MotionEvent)}. This separation of concerns
* reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
* and should not be expected to handle other pointing device features.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
* @hide
*/
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
這里會(huì)執(zhí)行decorView的dispatchTouchEvent:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
因?yàn)楫?dāng)前decorView注入的窗口是在創(chuàng)建dialog時(shí)新建的渠抹,另外dialog本身就是實(shí)現(xiàn)了Window.Callback接口蝙昙,第一步當(dāng)中已經(jīng)將dialog注入到window當(dāng)中了,因此此處的cb對(duì)象就是dialog對(duì)象梧却,這時(shí)就直接調(diào)用了dialog的dispatchTouchEvent方法奇颠。接下來(lái)就是熟知的事件分發(fā)流程了。此處便不再多做介紹放航。
結(jié)論:
Activity的dispatchTouchEvent也是從對(duì)應(yīng)的decorView的dispatchTouchEvent中分發(fā)出來(lái)的烈拒,而Activity所處的decorView跟Dialog所處的decorView并不屬于同一個(gè),所以Activity自然接收不到任何點(diǎn)擊事件广鳍。