android PopupWindow點(diǎn)擊外部和返回鍵消失的解決方法
剛接手PopupWindow的時(shí)候,我們都可能覺(jué)得很簡(jiǎn)單,因?yàn)樗_實(shí)很簡(jiǎn)單,不過(guò)運(yùn)氣不好的可能就會(huì)踩到一個(gè)坑:
點(diǎn)擊PopupWindow最外層布局以及點(diǎn)擊返回鍵PopupWindow不會(huì)消失
新手在遇到這個(gè)問(wèn)題的時(shí)候可能會(huì)折騰半天,最后通過(guò)強(qiáng)大的網(wǎng)絡(luò)找到一個(gè)解決方案,那就是跟PopupWindow設(shè)置一個(gè)背景
popupWindow.setBackgroundDrawable(drawable),這個(gè)drawable隨便一個(gè)什么類型的都可以,只要不為空。
Demo地址:SmartPopupWindow_jb51.rar
下面從源碼(我看的是android-22)上看看到底發(fā)生了什么事情導(dǎo)致返回鍵不能消失彈出框:
先看看彈出框顯示的時(shí)候代碼showAsDropDown,里面有個(gè)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不為空的時(shí)候,會(huì)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) { // 這個(gè)方法里面實(shí)現(xiàn)了返回鍵處理邏輯,會(huì)調(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) { // 這個(gè)方法里面實(shí)現(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)注可以看出,這個(gè)內(nèi)部類里面封裝了處理返回鍵退出和點(diǎn)擊外部退出的邏輯,但是這個(gè)類對(duì)象的構(gòu)造過(guò)程中(preparePopup方法中)卻有個(gè)mBackground != null的條件才會(huì)創(chuàng)建
而mBackground對(duì)象在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)造方法中也會(huì)調(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); }
有些版本沒(méi)有,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; }
這里實(shí)現(xiàn)返回鍵監(jiān)聽(tīng)的代碼是mDecorView = createDecorView(mBackgroundView),這個(gè)并沒(méi)有受到那個(gè)mBackground變量的控制,所以這個(gè)版本應(yīng)該沒(méi)有我們所描述的問(wèn)題,感興趣的可以自己去嘗試一下
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
OpenGL Shader實(shí)現(xiàn)物件材料效果詳解
在一些主流app上有一些比較特殊的濾鏡效果,例如灰塵、塑料封面、光影效果等,這些其實(shí)是紋理疊加的效果。本文將用OpenGL Shader實(shí)現(xiàn)這些效果,需要的可以參考一下2022-02-02使用ViewPager實(shí)現(xiàn)android軟件使用向?qū)Чδ軐?shí)現(xiàn)步驟
現(xiàn)在的大部分android軟件,都是使用說(shuō)明,就是第一次使用該軟件時(shí),會(huì)出現(xiàn)向?qū)?,可以左右滑?dòng),然后就進(jìn)入應(yīng)用的主界面了,下面我們就實(shí)現(xiàn)這個(gè)功能2013-11-11詳解Android短信的發(fā)送和廣播接收實(shí)現(xiàn)短信的監(jiān)聽(tīng)
本篇文章主要介紹了Android短信的發(fā)送和廣播接收實(shí)現(xiàn)短信的監(jiān)聽(tīng),可以實(shí)現(xiàn)短信收發(fā),有興趣的可以了解一下。2016-11-11基于Flutter制作一個(gè)吃豆人加載動(dòng)畫(huà)
這篇文章主要為大家介紹了如何利用Flutter制作出吃豆人加載動(dòng)畫(huà)效果,文中的示例代碼講解詳細(xì),快跟隨小編一起動(dòng)手嘗試一下2022-04-04手把手教你Android全局觸摸事件監(jiān)聽(tīng)
這篇文章主要介紹了Android全局觸摸事件監(jiān)聽(tīng),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09Android中用MediaRecorder進(jìn)行錄影的實(shí)例代碼
這篇文章主要介紹了Android中用MediaRecorder進(jìn)行錄影的實(shí)例代碼,有需要的朋友可以參考一下2014-01-01Android開(kāi)發(fā)入門(mén)之Notification用法分析
這篇文章主要介紹了Android中Notification用法,較為詳細(xì)的分析了Notification的功能、使用步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-07-07Android中應(yīng)用前后臺(tái)切換監(jiān)聽(tīng)的實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了關(guān)于Android中應(yīng)用前后臺(tái)切換監(jiān)聽(tīng)實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07Android TV開(kāi)發(fā):實(shí)現(xiàn)3D仿Gallery效果的實(shí)例代碼
這篇文章主要介紹了Android TV開(kāi)發(fā):實(shí)現(xiàn)3D仿Gallery效果的實(shí)例代碼,效果:滾動(dòng)翻頁(yè)+ 頁(yè)面點(diǎn)擊+頁(yè)碼指示器+焦點(diǎn)控制,主要為了移植到電視上做了按鍵事件和焦點(diǎn)控制。2018-05-05