目錄介紹
- 1.最簡單的建立方法
- 1.1 PopupWindow構造方法
- 1.2 顯示PopupWindow
- 1.3 最簡單的建立
- 1.4 注意問題寬和高屬性
- 2.源碼分析
- 2.1 setContentView(View contentView)
- 2.2 showAsDropDown()源碼
- 2.3 dismiss()源碼分析
- 2.4 PopupDecorView源碼分析
- 3.經典總結
- 3.1 PopupWindow和Dialog有什麼差別?
- 3.2 建立和銷毀的大概流程
- 3.3 為何彈窗點選一下就dismiss呢?
- 4.PopupWindow封裝庫介紹
好消息
- 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護并且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
- 連結位址: https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
- PopupWindow封裝庫項目位址: https://github.com/yangchong211/YCDialog
- 02.Toast源碼深度分析
- 最簡單的建立,簡單改造避免重複建立,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動銷毀的,TN類中的消息機制是如何執行的,普通應用的Toast顯示數量是有限制的,用代碼解釋為何Activity銷毀後Toast仍會顯示,Toast偶爾報錯Unable to add window是如何産生的,Toast運作在子線程問題,Toast如何添加系統視窗的權限等等
- 03.DialogFragment源碼分析
- 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析
- 05.PopupWindow源碼分析
- 顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什麼差別?為何彈窗點選一下就dismiss呢?
- 06.Snackbar源碼分析
- 最簡單的建立,Snackbar的make方法源碼分析,Snackbar的show顯示與點選消失源碼分析,顯示和隐藏中動畫源碼分析,Snackbar的設計思路,為什麼Snackbar總是顯示在最下面
- 07.彈窗常見問題
- DialogFragment使用中show()方法遇到的IllegalStateException,什麼常見産生的?Toast偶爾報錯Unable to add window,Toast運作在子線程導緻崩潰如何解決?
- 如下所示
public PopupWindow (Context context)
public PopupWindow(View contentView)
public PopupWindow(int width, int height)
public PopupWindow(View contentView, int width, int height)
public PopupWindow(View contentView, int width, int height, boolean focusable)
-
showAsDropDown(View anchor):相對某個控件的位置(正左下方),無偏移
showAsDropDown(View anchor, int xoff, int yoff):相對某個控件的位置,有偏移
showAtLocation(View parent, int gravity, int x, int y):相對于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以設定偏移或無偏移
- 具體如下所示
//建立對象
PopupWindow popupWindow = new PopupWindow(this);
View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
//設定view布局
popupWindow.setContentView(inflate);
popupWindow.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
//設定動畫的方法
popupWindow.setAnimationStyle(R.style.BottomDialog);
//設定PopUpWindow的焦點,設定為true之後,PopupWindow内容區域,才可以響應點選事件
popupWindow.setTouchable(true);
//設定背景透明
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
//點選空白處的時候讓PopupWindow消失
popupWindow.setOutsideTouchable(true);
// true時,點選傳回鍵先消失 PopupWindow
// 但是設定為true時setOutsideTouchable,setTouchable方法就失效了(點選外部不消失,内容區域也不響應事件)
// false時PopupWindow不處理傳回鍵,預設是false
popupWindow.setFocusable(false);
//設定dismiss事件
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
}
});
boolean showing = popupWindow.isShowing();
if (!showing){
//show,并且可以設定位置
popupWindow.showAsDropDown(mTv1);
}
2.1 setContentView(View contentView)源碼分析
- 首先先來看看源碼
- 可以看出,先判斷是否show,如果沒有showing的話,則進行contentView指派,如果mWindowManager為null,則取擷取mWindowManager,這個很重要。最後便是根據SDK版本而不是在構造函數中設定附加InDecor的預設設定,因為構造函數中可能沒有上下文對象。我們隻想在這裡設定預設,如果應用程式尚未設定附加InDecor。
public void setContentView(View contentView) {
//判斷是否show,如果已經show,則傳回
if (isShowing()) {
return;
}
//指派
mContentView = contentView;
if (mContext == null && mContentView != null) {
mContext = mContentView.getContext();
}
if (mWindowManager == null && mContentView != null) {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
//在這裡根據SDK版本而不是在構造函數中設定附加InDecor的預設設定,因為構造函數中可能沒有上下文對象。我們隻想在這裡設定預設,如果應用程式尚未設定附加InDecor。
if (mContext != null && !mAttachedInDecorSet) {
setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.LOLLIPOP_MR1);
}
}
- 接着來看一下setAttachedInDecor源碼部分
- 執行setAttachedInDecor給一個變量指派為true,表示已經在decor裡注冊了(注意:現在還沒有使用WindowManager把PopupWindow添加到DecorView上)
public void setAttachedInDecor(boolean enabled) {
mAttachedInDecor = enabled;
mAttachedInDecorSet = true;
}
- 先來看一下showAsDropDown(View anchor)部分代碼
- 可以看出,調用這個方法,預設偏移值都是0;關于這個attachToAnchor(anchor, xoff, yoff, gravity)方法作用,下面再說。之後通過createPopupLayoutParams方法建立和初始化LayoutParams,然後把這個LayoutParams傳過去,把PopupWindow真正的樣子,也就是view建立出來。
public void showAsDropDown(View anchor) {
showAsDropDown(anchor, 0, 0);
}
//主要看這個方法
//注意啦:關于更多内容,可以參考我的部落格大彙總:https://github.com/yangchong211/YCBlogs
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
//下面單獨講
//https://github.com/yangchong211/YCBlogs
attachToAnchor(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
//通過createPopupLayoutParams方法建立和初始化LayoutParams
final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
p.width, p.height, gravity);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
invokePopup(p);
}
- 接着來看看attachToAnchor(anchor, xoff, yoff, gravity)源碼
- 執行了一個attachToAnchor,意思是PopupWindow類似一個錨挂在目标view的下面,這個函數主要講xoff、yoff(x軸、y軸偏移值)、gravity(比如Gravity.BOTTOM之類,指的是PopupWindow放在目标view哪個方向邊緣的位置)這個attachToAnchor有點意思,通過弱引用儲存目标view和目标view的rootView(我們都知道:通過弱引用和軟引用可以防止記憶體洩漏)、這個rootview是否依附在window、還有儲存偏內插補點、gravity
- 關于四種引用的深入介紹可以參考我的這邊文章: 01.四種引用比較與源碼分析
private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
detachFromAnchor();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
if (vto != null) {
vto.addOnScrollChangedListener(mOnScrollChangedListener);
}
final View anchorRoot = anchor.getRootView();
anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
mAnchor = new WeakReference<>(anchor);
mAnchorRoot = new WeakReference<>(anchorRoot);
mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
mAnchorXoff = xoff;
mAnchorYoff = yoff;
mAnchoredGravity = gravity;
}
- 接着再來看看preparePopup(p)這個方法源碼
- 把這個LayoutParams傳過去,把PopupWindow真正的樣子,也就是view建立出來,在這個preparePopup函數裡,一開始準備backgroundView,因為一般mBackgroundView是null,是以把之前setContentView設定的contentView作為mBackgroundView。
- 接着看看createDecorView(mBackgroundView)這個方法源碼
- 把PopupWindow的根view建立出來,并把contentView通過addView方法添加進去。PopupDecorView繼承FrameLayout,其中沒有繪畫什麼,隻是複寫了dispatchKeyEvent和onTouchEvent之類的事件分發的函數,還有實作進場退場動畫的執行函數
- 最後看看invokePopup(WindowManager.LayoutParams p)源碼
- 執行invokePopup(p),這個函數主要将popupView添加到應用DecorView的相應位置,通過之前建立WindowManager完成這個步驟,現在PopupWIndow可以看得到。
- 并且請求在下一次布局傳遞之後運作Enter轉換。
- 通過對象調用該方法可以達到銷毀彈窗的目的。
- 接着看看dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)源碼
- 第一步,通過WindowManager登出PopupView
- 第二步,PopupView移除contentView
- 第三步,講mDecorView,mBackgroundView置為null
private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
// If this method gets called and the decor view doesn't have a parent,
// then it was either never added or was already removed. That should
// never happen, but it's worth checking to avoid potential crashes.
if (decorView.getParent() != null) {
mWindowManager.removeViewImmediate(decorView);
}
if (contentHolder != null) {
contentHolder.removeView(contentView);
}
// This needs to stay until after all transitions have ended since we
// need the reference to cancel transitions in preparePopup().
mDecorView = null;
mBackgroundView = null;
mIsTransitioningToDismiss = false;
}
- 兩者最根本的差別在于有沒有建立一個window,PopupWindow沒有建立,而是将view加到DecorView;Dialog是建立了一個window,相當于走了一遍Activity中建立window的流程
- 從源碼中可以看出,PopupWindow最終是執行了mWindowManager.addView方法,全程沒有建立window
- 源碼比較少,比較容易懂,即使不太懂,隻要借助有道詞典翻譯一下英文注釋,還是可以搞明白的。
- 總結一下PopupWindow的建立出現、消失有哪些重要操作
- 建立PopupWindow的時候,先建立WindowManager,因為WIndowManager擁有控制view的添加和删除、修改的能力。這一點關于任主席的藝術探索書上寫的很詳細……
- 然後是setContentView,儲存contentView,這個步驟就做了這個
- 顯示PopupWindow,這個步驟稍微複雜點,建立并初始化LayoutParams,設定相關參數,作為以後PopupWindow在應用DecorView裡哪裡顯示的憑據。然後建立PopupView,并且将contentView插入其中。最後使用WindowManager将PopupView添加到應用DecorView裡。
- 銷毀PopupView,WindowManager把PopupView移除,PopupView再把contentView移除,最後把對象置為null
- PopupWindow通過為傳入的View添加一層包裹的布局,并重寫該布局的點選事件,實作點選PopupWindow之外的區域PopupWindow消失的效果
項目位址:
- 鍊式程式設計,十分友善,更多内容可以直接參考我的開源demo
new CustomPopupWindow.PopupWindowBuilder(this)
//.setView(R.layout.pop_layout)
.setView(contentView)
.setFocusable(true)
//彈出popWindow時,背景是否變暗
.enableBackgroundDark(true)
//控制亮度
.setBgDarkAlpha(0.7f)
.setOutsideTouchable(true)
.setAnimationStyle(R.style.popWindowStyle)
.setOnDissmissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
//對話框銷毀時
}
})
.create()
.showAsDropDown(tv6,0,10);
關于其他内容介紹
01.關于部落格彙總連結
02.關于我的部落格