android PopupWindow點(diǎn)擊外部和返回鍵消失的解決方法
剛接手PopupWindow的時候,我們都可能覺得很簡單,因為它確實很簡單,不過運(yùn)氣不好的可能就會踩到一個坑:
點(diǎn)擊PopupWindow最外層布局以及點(diǎn)擊返回鍵PopupWindow不會消失
新手在遇到這個問題的時候可能會折騰半天,最后通過強(qiáng)大的網(wǎng)絡(luò)找到一個解決方案,那就是跟PopupWindow設(shè)置一個背景
popupWindow.setBackgroundDrawable(drawable),這個drawable隨便一個什么類型的都可以,只要不為空。
Demo地址:SmartPopupWindow_jb51.rar
下面從源碼(我看的是android-22)上看看到底發(fā)生了什么事情導(dǎo)致返回鍵不能消失彈出框:
先看看彈出框顯示的時候代碼showAsDropDown,里面有個preparePopup方法。
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { if (isShowing() || mContentView == null) { return; } registerForScrollChanged(anchor, xoff, yoff, gravity); mIsShowing = true; mIsDropdown = true; WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); preparePopup(p); updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; p.windowAnimations = computeAnimationResource(); invokePopup(p); }
再看preparePopup方法
/** * <p>Prepare the popup by embedding in into a new ViewGroup if the * background drawable is not null. If embedding is required, the layout * parameters' height is modified to take into account the background's * padding.</p> * * @param p the layout parameters of the popup's content view */ 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."); } if (mBackground != null) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); int height = ViewGroup.LayoutParams.MATCH_PARENT; if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { height = ViewGroup.LayoutParams.WRAP_CONTENT; } // when a background is available, we embed the content view // within another view that owns the background drawable PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; }
上面可以看到mBackground不為空的時候,會PopupViewContainer作為mContentView的Parent,下面看看PopupViewContainer到底干了什么
private class PopupViewContainer extends FrameLayout { private static final String TAG = "PopupWindow.PopupViewContainer"; public PopupViewContainer(Context context) { super(context); } @Override protected int[] onCreateDrawableState(int extraSpace) { if (mAboveAnchor) { // 1 more needed for the above anchor state final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); return drawableState; } else { return super.onCreateDrawableState(extraSpace); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { // 這個方法里面實現(xiàn)了返回鍵處理邏輯,會調(diào)用dismiss if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (getKeyDispatcherState() == null) { return super.dispatchKeyEvent(event); } if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { dismiss(); return true; } } return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // 這個方法里面實現(xiàn)點(diǎn)擊消失邏輯 final int x = (int) event.getX(); final int y = (int) event.getY(); if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); return true; } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } else { return super.onTouchEvent(event); } } @Override public void sendAccessibilityEvent(int eventType) { // clinets are interested in the content not the container, make it event source if (mContentView != null) { mContentView.sendAccessibilityEvent(eventType); } else { super.sendAccessibilityEvent(eventType); } } }
看到上面紅色部分的標(biāo)注可以看出,這個內(nèi)部類里面封裝了處理返回鍵退出和點(diǎn)擊外部退出的邏輯,但是這個類對象的構(gòu)造過程中(preparePopup方法中)卻有個mBackground != null的條件才會創(chuàng)建
而mBackground對象在setBackgroundDrawable方法中被賦值,看到這里應(yīng)該就明白一切了。
/** * Specifies the background drawable for this popup window. The background * can be set to {@code null}. * * @param background the popup's background * @see #getBackground() * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setBackgroundDrawable(Drawable background) { mBackground = background; // 省略其他的 }
setBackgroundDrawable方法除了被外部調(diào)用,構(gòu)造方法中也會調(diào)用,默認(rèn)是從系統(tǒng)資源中取的
/** * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> * * <p>The popup does not provide a background.</p> */ public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes); final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground); mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle; a.recycle(); setBackgroundDrawable(bg); }
有些版本沒有,android6.0版本preparePopup如下:
/** * Prepare the popup by embedding it into a new ViewGroup if the background * drawable is not null. If embedding is required, the layout parameters' * height is modified to take into account the background's padding. * * @param p the layout parameters of the popup's content view */ 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; }
這里實現(xiàn)返回鍵監(jiān)聽的代碼是mDecorView = createDecorView(mBackgroundView),這個并沒有受到那個mBackground變量的控制,所以這個版本應(yīng)該沒有我們所描述的問題,感興趣的可以自己去嘗試一下
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用ViewPager實現(xiàn)android軟件使用向?qū)Чδ軐崿F(xiàn)步驟
現(xiàn)在的大部分android軟件,都是使用說明,就是第一次使用該軟件時,會出現(xiàn)向?qū)?,可以左右滑動,然后就進(jìn)入應(yīng)用的主界面了,下面我們就實現(xiàn)這個功能2013-11-11詳解Android短信的發(fā)送和廣播接收實現(xiàn)短信的監(jiān)聽
本篇文章主要介紹了Android短信的發(fā)送和廣播接收實現(xiàn)短信的監(jiān)聽,可以實現(xiàn)短信收發(fā),有興趣的可以了解一下。2016-11-11Android中用MediaRecorder進(jìn)行錄影的實例代碼
這篇文章主要介紹了Android中用MediaRecorder進(jìn)行錄影的實例代碼,有需要的朋友可以參考一下2014-01-01Android開發(fā)入門之Notification用法分析
這篇文章主要介紹了Android中Notification用法,較為詳細(xì)的分析了Notification的功能、使用步驟與相關(guān)注意事項,需要的朋友可以參考下2016-07-07Android中應(yīng)用前后臺切換監(jiān)聽的實現(xiàn)詳解
這篇文章主要給大家介紹了關(guān)于Android中應(yīng)用前后臺切換監(jiān)聽實現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07Android TV開發(fā):實現(xiàn)3D仿Gallery效果的實例代碼
這篇文章主要介紹了Android TV開發(fā):實現(xiàn)3D仿Gallery效果的實例代碼,效果:滾動翻頁+ 頁面點(diǎn)擊+頁碼指示器+焦點(diǎn)控制,主要為了移植到電視上做了按鍵事件和焦點(diǎn)控制。2018-05-05