2012年2月15日水曜日

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

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

---


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

PopupWindow.java#1578 1578 @Override 1579 public boolean onTouchEvent(MotionEvent event) { 1580 final int x = (int) event.getX(); 1581 final int y = (int) event.getY(); 1582 1583 if ((event.getAction() == MotionEvent.ACTION_DOWN) 1584 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 1585 dismiss(); 1586 return true; 1587 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 1588 dismiss(); 1589 return true; 1590 } else { 1591 return super.onTouchEvent(event); 1592 } 1593 }

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

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

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

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

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

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

312 public Drawable getBackground() { 313 return mBackground; 314 }

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

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

PopupWindow.java#944 944 private void preparePopup(WindowManager.LayoutParams p) { ... 950 if (mBackground != null) { ... 960 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); 961 PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( 962 ViewGroup.LayoutParams.MATCH_PARENT, height 963 ); 964 popupViewContainer.setBackgroundDrawable(mBackground); 965 popupViewContainer.addView(mContentView, listParams); 966 967 mPopupView = popupViewContainer; 968 } else { 969 mPopupView = mContentView; 970 } ... 973 }

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

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


3 件のコメント:

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

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

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

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

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

    返信削除