不得不吐槽的Android PopupWindow的幾個痛點(實現帶箭頭的上下文菜單遇到的坑)


  說到PopupWindow,我個人感覺是又愛又恨,沒有深入使用之前總覺得這個東西應該很簡單,很好用,但是真正使用PopupWindow實現一些效果的時候總會遇到一些問題,但是即便是人家的api有問題,作為程序員也沒有辦法,只能去想辦法去補救。

下面是我在使用過程中發現的關於PopupWindow的幾個痛點:

  痛點一:不設置背景就不能響應返回鍵和點擊外部消失的,這個我已經有一篇文章進行分析過http://www.cnblogs.com/popfisher/p/5608717.html,這個我認為就是api留下的bug,有些版本里面修復了這個問題,感興趣的可以多看看幾個版本的源碼,還可以看出Google是怎么修改的。

  痛點二:showAsDropDown(View anchorView)方法使用也會遇到坑,如果不看api注釋,會認為PopupWindow只能顯示在anchorView的下面(與anchorView左下角對齊顯示),但是看了方法注釋之后發現此方法是可以讓PopupWindow顯示在anchorView的上面的(anchorView左上角對齊顯示)。如果真這樣,那實現自適應帶箭頭的上下文菜單不就很容易了么,事實證明還是會有些瑕疵。

  痛點三:個人覺得api設計得不好使,不過這個只能怪自己對api理解不夠深刻,不過下面幾個api組合使用還是得介紹一下。

// 如果不設置PopupWindow的背景,有些版本就會出現一個問題:無論是點擊外部區域還是Back鍵都無法dismiss彈框
popupWindow.setBackgroundDrawable(new ColorDrawable()); // setOutsideTouchable設置生效的前提是setTouchable(true)和setFocusable(false)
popupWindow.setOutsideTouchable(true); // 設置為true之后,PopupWindow內容區域 才可以響應點擊事件
popupWindow.setTouchable(true); // true時,點擊返回鍵先消失 PopupWindow // 但是設置為true時setOutsideTouchable,setTouchable方法就失效了(點擊外部不消失,內容區域也不響應事件) // false時PopupWindow不處理返回鍵
popupWindow.setFocusable(false); popupWindow.setTouchInterceptor(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false;   // 這里面攔截不到返回鍵
 } });

 

  將理論始終聽起來很形象,通過實例可以讓人更加印象深刻,第一點已經有文章介紹了,下面實現一個帶箭頭的上下文菜單體會一下痛點二和三,到底怎么個痛法。先上效果再上代碼,代碼里面的注釋標注了痛點的地方。

上下文菜單效果圖

默認向下彈出

下面空間不足時先上彈出

 特例出現了,我希望第一排右邊按鈕點擊時PopupWindow在下面,但是我失望了

 

雖然達不到我要的效果,但是作為學習資源還是不錯的,下面貼出代碼

import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.Toast; public class TopBottomArrowPopupActivity extends Activity implements View.OnClickListener { private View mButton1; private View mButton2; private View mButton3; private View mButton4; private View mButton5; private View mButton6; private PopupWindow mCurPopupWindow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_arrow_pos_window); mButton1 = findViewById(R.id.buttion1); mButton2 = findViewById(R.id.buttion2); mButton3 = findViewById(R.id.buttion3); mButton4 = findViewById(R.id.buttion4); mButton5 = findViewById(R.id.buttion5); mButton6 = findViewById(R.id.buttion6); mButton1.setOnClickListener(this); mButton2.setOnClickListener(this); mButton3.setOnClickListener(this); mButton4.setOnClickListener(this); mButton5.setOnClickListener(this); mButton6.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.buttion1: mCurPopupWindow = showTipPopupWindow(mButton1, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; case R.id.buttion2: mCurPopupWindow = showTipPopupWindow(mButton2, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; case R.id.buttion3: mCurPopupWindow = showTipPopupWindow(mButton3, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; case R.id.buttion4: mCurPopupWindow = showTipPopupWindow(mButton4, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; case R.id.buttion5: showTipPopupWindow(mButton5, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; case R.id.buttion6: mCurPopupWindow = showTipPopupWindow(mButton6, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "點擊到彈窗內容", Toast.LENGTH_SHORT).show(); } }); break; } } public PopupWindow showTipPopupWindow(final View anchorView, final View.OnClickListener onClickListener) { final View contentView = LayoutInflater.from(anchorView.getContext())
                  .inflate(R.layout.popuw_content_top_arrow_layout, null); contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); // 創建PopupWindow時候指定高寬時showAsDropDown能夠自適應 // 如果設置為wrap_content,showAsDropDown會認為下面空間一直很充足(我以認為這個Google的bug) // 備注如果PopupWindow里面有ListView,ScrollView時,一定要動態設置PopupWindow的大小 final PopupWindow popupWindow = new PopupWindow(contentView, contentView.getMeasuredWidth(), contentView.getMeasuredHeight(), false); contentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { popupWindow.dismiss(); onClickListener.onClick(v); } }); contentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 自動調整箭頭的位置 autoAdjustArrowPos(popupWindow, contentView, anchorView); contentView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); // 如果不設置PopupWindow的背景,有些版本就會出現一個問題:無論是點擊外部區域還是Back鍵都無法dismiss彈框 popupWindow.setBackgroundDrawable(new ColorDrawable()); // setOutsideTouchable設置生效的前提是setTouchable(true)和setFocusable(false) popupWindow.setOutsideTouchable(true); // 設置為true之后,PopupWindow內容區域 才可以響應點擊事件 popupWindow.setTouchable(true); // true時,點擊返回鍵先消失 PopupWindow // 但是設置為true時setOutsideTouchable,setTouchable方法就失效了(點擊外部不消失,內容區域也不響應事件) // false時PopupWindow不處理返回鍵 popupWindow.setFocusable(false); popupWindow.setTouchInterceptor(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; // 這里面攔截不到返回鍵 } }); // 如果希望showAsDropDown方法能夠在下面空間不足時自動在anchorView的上面彈出 // 必須在創建PopupWindow的時候指定高度,不能用wrap_content popupWindow.showAsDropDown(anchorView); return popupWindow; } private void autoAdjustArrowPos(PopupWindow popupWindow, View contentView, View anchorView) { View upArrow = contentView.findViewById(R.id.up_arrow); View downArrow = contentView.findViewById(R.id.down_arrow); int pos[] = new int[2]; contentView.getLocationOnScreen(pos); int popLeftPos = pos[0]; anchorView.getLocationOnScreen(pos); int anchorLeftPos = pos[0]; int arrowLeftMargin = anchorLeftPos - popLeftPos + anchorView.getWidth() / 2 - upArrow.getWidth() / 2; upArrow.setVisibility(popupWindow.isAboveAnchor() ? View.INVISIBLE : View.VISIBLE); downArrow.setVisibility(popupWindow.isAboveAnchor() ? View.VISIBLE : View.INVISIBLE); RelativeLayout.LayoutParams upArrowParams = (RelativeLayout.LayoutParams) upArrow.getLayoutParams(); upArrowParams.leftMargin = arrowLeftMargin; RelativeLayout.LayoutParams downArrowParams = (RelativeLayout.LayoutParams) downArrow.getLayoutParams(); downArrowParams.leftMargin = arrowLeftMargin; } @Override public void onBackPressed() { if (mCurPopupWindow != null && mCurPopupWindow.isShowing()) { mCurPopupWindow.dismiss(); } else { super.onBackPressed(); } } }

 

結束語

  雖然不能完全把PopupWindow的問題描述清楚,但是只要知道有這些坑,以后寫代碼的時候就會多留意下,知道PopupWindow的那幾個常用api相互組合會出現什么樣的結果。堅持寫文章不容易,但是感覺遇到的問題就應該記錄下來,好記性不如爛筆頭,時間長了可以通過文章記錄的知識快速為自己找到問題的解決方法。

  有需要源碼可以點擊下載地址 https://github.com/PopFisher/SmartPopupWindow 上面還有關於PopupWindow的一些其他用法,遇到新的問題時會更新記錄一下

思考:怎么使得PopupWindow可以實現點擊外部可以消失,內容區域可以響應點擊事件,同時還能攔截返回鍵?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM