弹出窗口 - 点击外部区域时关闭

113

我在我的活动中有一个PopupWindow,问题是即使我与我的活动交互(比如滚动列表),PopupWindow仍然显示。我可以滚动列表而PopupWindow仍然存在。

我想实现的是,当我触摸/滚动/点击等与PopupWindow不相关的屏幕时,我想关闭PopupWindow。就像菜单一样工作。如果你点击菜单外面,菜单就会被关闭。

我尝试过使用setOutsideTouchable(true),但它无法关闭窗口。谢谢。

15个回答

140

我发现除了WareNinja在已被接受的答案中的评论和Marcin S.提供的答案可能也适用外,其他答案对我都不起作用。以下是对我有效的部分:

myPopupWindow.setBackgroundDrawable(new BitmapDrawable());
myPopupWindow.setOutsideTouchable(true);

或者:

myPopupWindow.setFocusable(true);

不确定它们之间的区别是什么,但是ListPopupWindow源代码在使用setModal将其模态设置为true时实际上使用了后者,因此至少Android开发人员认为这是一种可行的方法,且只需一行代码。


6
非常感谢。其他答案对我都没有用或者解释得不够清楚。不过第二个选项对我也没有用。 - JDN
2
我还注意到BitmapDrawable已经被弃用了。如果能有一个真正的解决方案就太好了,因为这些看起来像是暂时性的解决方法,不能保证在新的API版本中得到支持。 - HAL9000
1
为了避免使用BitmapDrawable过时的构造函数,请参考这里:https://dev59.com/uWIk5IYBdhLWcg3wiuqk#21680637。popupWindow.setBackgroundDrawable(new BitmapDrawable(getResources(), "")); - nommer
在使用“setFocusable”的替代方法时,我们需要点击两次按钮(其中按钮位于弹出窗口之外),而在第一种方法中它可以正常工作 :) - Joy Rex
BitmapDrawable()已经过时,请使用ColorDrawable()代替。 - Srujan Barai
在显示你的弹出窗口前,一定要添加这段代码。 - snersesyan

138
请尝试在PopupWindow上设置setBackgroundDrawable,这样当您在窗口外部触摸时,它应该会关闭。

5
我错过了。你在弹出窗口上使用了setBackgroundDrawable吗?我知道将背景drawable设置为null会使OnTouchListener失效。 - Marcin S.
34
就是这样!谢谢啊!在这种情况下,甚至触摸事件也可以得到适当的处理。popupWindow.setOutsideTouchable(true); popupWindow.setTouchable(true); popupWindow.setBackgroundDrawable(new BitmapDrawable()); popupWindow.setTouchInterceptor(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (AppContext.isDebugMode()) Log.d("POPUP_WINDOW", "v: "+v.getTag() + " | event: "+event.getAction()); popupWindow.dismiss(); return true; } }); - Yilmaz Guleryuz
3
将背景可绘制对象设置为 null 对我无效。如果其他人也遇到了这个问题,请查看我的答案。 - mpellegr
2
@WareNinja,你的评论起作用了!也许你最好留下一个完整的答案来回答这个问题,这对其他人会很有用。 - Anton Kizema
4
@WareNinja BitmapDrawable()已经过时,请使用ColorDrawable()代替。 - Srujan Barai
显示剩余5条评论

70

我遇到了相同的问题,按照以下代码进行了修复。这对我来说很有效。

    // Closes the popup window when touch outside.
    mPopupWindow.setOutsideTouchable(true);
    mPopupWindow.setFocusable(true);
    // Removes default background.
    mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

顺便提一下,不要使用BitmapDrawable的过时构造函数,使用new ColorDrawable(android.R.color.transparent)来替换默认背景。

祝你玩得愉快@.@


4
在展示弹出窗口之前,请确保加入这段代码。 - snersesyan
如果弹出窗口不需要焦点,我是否真的需要将其设置为可聚焦? - Taras Lozovyi
1
我很惊讶这个能够工作,但在API 21上是必需的。这也修复了我的弹出窗口动画不正确的问题。 - EpicPandaForce

25

我知道现在已经很晚了,但我注意到人们仍然在使用弹出窗口时会遇到问题。因此,我决定写一个完全可用的示例,您可以通过点击窗口外部或窗口本身来关闭弹出窗口。要做到这一点,请创建一个新的PopupWindow类并复制下面的代码:

PopupWindow.class

public class PopupWindow extends android.widget.PopupWindow
{
Context ctx;
Button btnDismiss;
TextView lblText;
View popupView;

public PopupWindow(Context context)
{
    super(context);

    ctx = context;
    popupView = LayoutInflater.from(context).inflate(R.layout.popup, null);
    setContentView(popupView);

    btnDismiss = (Button)popupView.findViewById(R.id.btn_dismiss);
    lblText = (TextView)popupView.findViewById(R.id.text);

    setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
    setWidth(WindowManager.LayoutParams.WRAP_CONTENT);

    // Closes the popup window when touch outside of it - when looses focus
    setOutsideTouchable(true);
    setFocusable(true);

    // Removes default black background
    setBackgroundDrawable(new BitmapDrawable());

    btnDismiss.setOnClickListener(new Button.OnClickListener(){

        @Override
        public void onClick(View v) {


         dismiss();
        }});

    // Closes the popup window when touch it
/*     this.setTouchInterceptor(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                dismiss();
            }
            return true;
        }
    }); */   
   } // End constructor

   // Attaches the view to its parent anchor-view at position x and y
   public void show(View anchor, int x, int y)
   {
      showAtLocation(anchor, Gravity.CENTER, x, y);
   }
}

现在为弹出窗口创建布局: popup.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout     
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="1dp"
    android:orientation="vertical"
    android:padding="10dp" >

<TextView 
    android:id="@+id/text" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"  
    android:gravity="center" 
    android:padding="5dp" 
    android:text="PopupWindow Example"
    android:textColor="#000000" 
    android:textSize="17sp" 
    android:textStyle="italic" />

<FrameLayout
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:layout_gravity="center_vertical">

    <Button
        android:id="@+id/btn_dismiss" 
        style="?android:attr/buttonStyleSmall" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="Dismiss" 
        android:visibility="gone" />

    <TextView
        android:id="@+id/lbl_dismiss"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Touch outside of this box to dismiss"
        android:textColor="#ffffff"
        android:textStyle="bold" />

</FrameLayout>      

在您的主活动中创建 PopupWindow 类的一个实例:

final PopupWindow popupWindow = new PopupWindow(this);
popupWindow.show(findViewById(R.id.YOUR_MAIN_LAYOUT), 0, -250);

其中 YOUR_MAIN_LAYOUT 是当前活动的布局,在该布局中弹出 popupWindow。


1
谢谢 - 这对我有用。只是一个小建议,最好使用另一个名称来命名你的自定义类,而不是PopupWindow,可以称之为MyPopupWindow,这样Android就不会在你的自定义类和它自己的标准Android类之间产生混淆了。 - Simon
@Marcin S. findViewById(R.id.YOUR_MAIN_LAYOUT)??它会是R.layout.My_Layout吗? - Ankesh kumar Jaisansaria
@Simon findViewById(R.id.YOUR_MAIN_LAYOUT)?它会是R.layout.My_Layout吗? - Ankesh kumar Jaisansaria

18

感谢 @LunaKong 的回答和 @HourGlass 的确认。我不想发重复的评论,只想让它清晰简洁。

// Closes the popup window when touch outside. This method was written informatively in Google's docs.
mPopupWindow.setOutsideTouchable(true);

// Set focus true to prevent a touch event to go to a below view (main layout), which works like a dialog with 'cancel' property => Try it! And you will know what I mean.
mPopupWindow.setFocusable(true);

// Removes default background.
mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

Mttdat.


我想通过在弹出窗口外部单击来关闭它,但是当我这样做时,位于其下方的视图(不属于弹出窗口,而是活动的一部分)被点击。 setFocusable(true)就是我要找的东西。谢谢! - hellaandrew
@hellaandrew,很高兴能帮到你,:) - Nguyen Tan Dat

10

对于 ListPopupWindow,在显示时将窗口设置为模态。

mListPopupWindow.setModal(true);

这样,点击ListPopupWindow之外的区域将会关闭它。


谢谢,我正想找这个。这不仅可以在触摸视图外部后设置listpopupwindow可关闭,而且还不会将触摸事件传递给旁边的其他视图,这些视图位于listpopwindow旁边。在我的情况下,我迫切需要这个功能,因为触摸listpopwindow外部会将事件传递给recyclerview,而recyclerview项目会被选中,这不是我想要的结果。 - shankar_vl
您可能还需要使用 mListPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);,以防止弹出窗口干扰屏幕键盘。 - Mr-IDE

7
请注意,要使用popupWindow.setOutsideTouchable(true)取消弹出窗口,您需要像下面的代码一样将宽度和高度设置为wrap_content:
PopupWindow popupWindow = new PopupWindow(
            G.layoutInflater.inflate(R.layout.lay_dialog_support, null, false),
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT, true);

popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
popupWindow.setOutsideTouchable(true);
popupWindow.setFocusable(true);
popupWindow.showAtLocation(view, Gravity.RIGHT, 0, 0);

6

您可以使用isOutsideTouchable或者isFocusable来在触摸外部时关闭弹出窗口。

popupWindow.isOutsideTouchable = true // dismiss popupwindow when touch outside

popupWindow.isFocusable = true // dismiss popupwindow when touch outside AND when press back button

注意

  • 目前,在测试后我发现setBackgroundDrawable不能帮助我们关闭弹出窗口。

  • 如果您查看PopupWindow中dismiss的代码(PopupWindow->PopupDecorView->dispatchKeyEventPopupWindow->PopupDecorView->onTouchEvent)。您会发现当按下返回键时,它们在ACTION_UP上关闭,当触摸外部时,它们在ACTION_UPACTION_OUTSIDE上关闭。


5
  popupWindow.setTouchable(true);
  popupWindow.setFocusable(true);
  popupWindow.showAtLocation(popupView, Gravity.CENTER, 0, 0);

点击/触摸屏幕时,它将关闭弹出窗口。在showAtLocation之前,请确保已将focusable设置为true。

1
请添加一些解释性文本,以阐述如何提供准确的答案来回答问题。谢谢。 - philantrovert
谢谢!在调用showAtLocation()之前,您需要先调用setters。 - droid256

5
mPopWindow.setFocusable(true);

1
这是唯一需要的东西。我不明白为什么被接受的答案得到了如此高的赞同。 - sziraqui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接