繼上文講述了dialog的創(chuàng)建流程之后,接下來(lái)講一下popwindow的創(chuàng)建以及和dialog的不同之處
首先看任何代碼都要帶著疑問(wèn)去看览露,不然很容易一頭霧水觉义,先說(shuō)幾個(gè)問(wèn)題
1.popwindow的顯示分2種黑毅,一種是showAsDropDown和showAtLocation
其各自實(shí)現(xiàn)的原理
2.popwindow的動(dòng)畫(huà)是和dialog一樣是window的動(dòng)畫(huà)么
3.popwindow的點(diǎn)擊是如何處理的暑中?
4.popwindow中和dialog一樣新建了一個(gè)phonewindow,同時(shí)又用附屬的activity的windowManager添加的么其障?
首先簡(jiǎn)單看下顯示的代碼
View mPopView = getLayoutInflater().inflate(R.layout.popwindow_layout, null);
// 將轉(zhuǎn)換的View放置到 新建一個(gè)popuwindow對(duì)象中
mPopupWindow = new PopupWindow(mPopView,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT);
// 點(diǎn)擊popuwindow外讓其消失
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.showAsDropDown(mCenterTx, Gravity.CENTER, 0, 0);
可以看到用法和dialog如出一轍银室,主要看下其構(gòu)造和show的邏輯吧
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
可以看到用的還是依附的activity的windowManager,然后設(shè)置了幾個(gè)屬性励翼。
接下來(lái)看下關(guān)鍵的showAsDropDown方法
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
registerForScrollChanged(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
//關(guān)鍵點(diǎn)一
final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
preparePopup(p);關(guān)鍵點(diǎn)一
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
updateAboveAnchor(aboveAnchor);
//關(guān)鍵點(diǎn)二
invokePopup(p);
}
關(guān)鍵點(diǎn)一:
public IBinder getWindowToken() {
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
}
無(wú)論是popwindow還是dialog時(shí)蜈敢,show的時(shí)候都要綁定所屬activity的token,而mAttachInfo這個(gè)對(duì)象是viewRootImp初始化的時(shí)候新建的
而初始化的時(shí)機(jī)就是WindowManagerGlobal.addview時(shí)候所創(chuàng)建的
所以這個(gè)token是為null的汽抚,在onCreate時(shí)候
接下來(lái)看下createPopupLayoutParams方法
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location by
// setting the x and y offsets to match the anchor's bottom-left
// corner.
p.gravity = Gravity.START | Gravity.TOP;
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
很顯然抓狭,用的動(dòng)畫(huà)也是windowManger里屬性的動(dòng)畫(huà)
來(lái)看下關(guān)鍵點(diǎn)二
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
// The old decor view may be transitioning out. Make sure it finishes
// and cleans up before we try to create another one.
if (mDecorView != null) {
mDecorView.cancelTransitions();
}
// When a background is available, we embed the content view within
// another view that owns the background drawable.
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
可以看到popwindow并沒(méi)有像dialog一樣,新建了一個(gè)phonewindow,那它是window么造烁?肯定是辐宾,只不過(guò)它的windowManager.layoutParams的屬性要自己配置狱从,而且它也沒(méi)有常規(guī)setContentView的這么多的嵌套
那既然沒(méi)有新建phonewindow,那它的點(diǎn)擊事件的回掉又從那里來(lái)呢膨蛮?
private class PopupDecorView extends FrameLayout {
private TransitionListenerAdapter mPendingExitListener;
public PopupDecorView(Context context) {
super(context);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
....
可以看到它的點(diǎn)擊事件全部有自己的PopupDecorView重寫(xiě)了叠纹,并沒(méi)有遵循傳統(tǒng)的decorview的事件的傳遞方式,所以也沒(méi)有了回掉
關(guān)鍵點(diǎn)三
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
這里還是用activity的windowManager添加的敞葛,后續(xù)的操作和dialog的顯示是一樣的誉察,這里就不再特別分析了,有個(gè)關(guān)于token的地方要著重說(shuō)下
window的adjustLayoutParamsForSubWindow方法
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
if (curTitle == null || curTitle.length() == 0) {
String title;
if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
title="Media";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
title="MediaOvr";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
title="Panel";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
title="SubPanel";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
title="AboveSubPanel";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
title="AtchDlg";
} else {
title=Integer.toString(wp.type);
}
if (mAppName != null) {
title += ":" + mAppName;
}
wp.setTitle(title);
}
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
if ((curTitle == null || curTitle.length() == 0)
&& mAppName != null) {
wp.setTitle(mAppName);
}
}
if (wp.packageName == null) {
wp.packageName = mContext.getPackageName();
}
if (mHardwareAccelerated) {
wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
可以看到由于popwindow是子窗口類(lèi)型惹谐,token為null的情況下會(huì)調(diào)用activity的decorview的token持偏,當(dāng)然在oncreate時(shí)也為null了,而dialog就不同了氨肌,默認(rèn)給到的就是null鸿秆,走了else的邏輯直接把mApptoken也就是activity的token給到了,這也是為何popwindow不能再oncreate顯示而dialog可以的最根本原因
總結(jié)
先來(lái)說(shuō)下幾個(gè)問(wèn)題
1.顯示的位置其實(shí)和WindowManager.layoutParams有關(guān)showAsDropDown其實(shí)就是算出錨地的位置然后放到其下方怎囚,而showAtLocation沒(méi)有此段邏輯
2.動(dòng)畫(huà)的顯示卿叽,其實(shí)也是依靠WindowManager.layoutParam的屬性,可以說(shuō)一樣的
3.點(diǎn)擊區(qū)域的處理恳守,這個(gè)和dialog有所不同考婴,由于不是系統(tǒng)的decorview,所以重寫(xiě)了分發(fā)的方法催烘。
4.源碼中表明了都是windowManager添加的沥阱,但是popwindow并沒(méi)有新建phonewinow,其實(shí)新建一個(gè)子window只要windowManager和decorview就能新建,新建了phonewindow默認(rèn)的屬性就是應(yīng)用類(lèi)型的window而已伊群,這也是兩者最大的不同之處
很多人說(shuō)popwindow比dialog都要“輕”考杉,但是又說(shuō)不出個(gè)所以然來(lái),其實(shí)無(wú)非就是子window上不能新建子window舰始,而dialog上可以添加子window罷了
注意點(diǎn):
1.popwindow和dialog都會(huì)存在activity的token不存在了崇棠,而不能顯示的情況.要做好基類(lèi)的統(tǒng)一處理
2.popwinow一般用在依靠某個(gè)view顯示的位置情況,dialog則不然