2012年2月15日水曜日

Android コードから PopupWindow を生成するときは setBackgroundDrawable() したほうがいいよ

こんにちは。お久しぶりです。
原稿とか原稿とかメルマガとかで、ご無沙汰してたのですが、ぼちぼち再開します。
原稿の行方についてはそのうちご報告できると思います。(早くはじめにを書けよ、というry)

---


PopupWindow には、setOutsideTouchable(true) を指定すると、PopupWindow の外の領域をタップしたときにポップアップを閉じる処理が実装されています。

PopupWindow.java#1578
  1. 1578         @Override  
  2. 1579         public boolean onTouchEvent(MotionEvent event) {  
  3. 1580             final int x = (int) event.getX();  
  4. 1581             final int y = (int) event.getY();  
  5. 1582   
  6. 1583             if ((event.getAction() == MotionEvent.ACTION_DOWN)  
  7. 1584                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {  
  8. 1585                 dismiss();  
  9. 1586                 return true;  
  10. 1587             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {  
  11. 1588                 dismiss();  
  12. 1589                 return true;  
  13. 1590             } else {  
  14. 1591                 return super.onTouchEvent(event);  
  15. 1592             }  
  16. 1593         }  


また、setTouchInterceptor() で View.OnTouchListener を指定すると、ポップアップのタッチを取得することができます。
ちなみに、setOutsideTouchable(true) を指定しないと、ポップアップの外側をタッチしたときにリスナーが呼ばれません。

PopupWindow.java#1570
  1. 1570         @Override  
  2. 1571         public boolean dispatchTouchEvent(MotionEvent ev) {  
  3. 1572             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {  
  4. 1573                 return true;  
  5. 1574             }  
  6. 1575             return super.dispatchTouchEvent(ev);  
  7. 1576         }  


問題は、これらポップアップのタッチイベントが PopupWindow の mBackground 変数が null の時は呼ばれない!!!ということなのです。

レイアウトXMLファイルから PopupWindow を生成した場合は

  1. 177     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  2. 178         mContext = context;  
  3. 179         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);  
  4. 180   
  5. 181         TypedArray a =  
  6. 182             context.obtainStyledAttributes(  
  7. 183                 attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);  
  8. 184   
  9. 185         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);  


のようにデフォルトの背景が指定されるので問題ないのですが、コードから生成する場合は PopupWindow の setBackgroundDrawable() メソッドを読んで mBackground に Drawable をセットしないと

  1. 312     public Drawable getBackground() {  
  2. 313         return mBackground;  
  3. 314     }  


setOutsideTouchable(true) を指定してもポップアップの周りをタップしても自動で dismiss() してくれないし、さらには setTouchInterceptor() で指定したリスナーの onTouch() も呼ばれなくなります!

実は、これらのタッチイベントの処理 (onTouchEvent や dispatchTouchEvent) は PopupWindow のサブクラスの PopupViewContainer の中に実装されています。この PopupViewContainer が mBackground が null 以外のときだけ使うような実装になっているのです。

PopupWindow.java#944
  1. 944     private void preparePopup(WindowManager.LayoutParams p) {  
  2.             ...  
  3. 950         if (mBackground != null) {  
  4.                 ...  
  5. 960             PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);  
  6. 961             PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(  
  7. 962                     ViewGroup.LayoutParams.MATCH_PARENT, height  
  8. 963             );  
  9. 964             popupViewContainer.setBackgroundDrawable(mBackground);  
  10. 965             popupViewContainer.addView(mContentView, listParams);  
  11. 966   
  12. 967             mPopupView = popupViewContainer;  
  13. 968         } else {  
  14. 969             mPopupView = mContentView;  
  15. 970         }  
  16.             ...  
  17. 973     }  


別に背景なくても PopupViewContainer 使ってくれればいいのにー。

ということで、とりあえず PopupWindow は背景指定したほうがいいよー。


3 件のコメント:

  1. 助かりましたm(_._)m

    お礼と言ってはナンですが、Typoを指摘いたします。
    setTouchIntercepter()ではなく、setTouchInterceptor()ですよ。

    返信削除
    返信
    1. ありがとうございます。
      修正しました。

      削除
  2. ありがとうございます!
    同じ事象ではまっていたので助かりました。

    それにしてもtrueにするだけでは動かなくbackgroundを見ているとは。。。

    返信削除