【问题标题】:Avoiding PopupWindow dismissal after touching outside触摸外部后避免弹出窗口关闭
【发布时间】:2022-03-16 09:11:47
【问题描述】:

我想使用具有以下行为/功能的 PopupWindow:

  • 它是可聚焦的(例如按钮内部有交互式控件)
  • View 'under' popupwindow 必须正确使用弹出窗口外的触摸
  • .. 但即使在外部点击后,弹出窗口也必须留在屏幕上

我发现了一堆关于 PopupWindow 的帖子,但没有人问过如何处理这种情况..

我想我尝试了 setOutsideTouchable()、setFocusable()、setTouchable() 的所有可能组合,但我被卡住了。弹出窗口可以正确处理点击,但在触摸外部时它总是被关闭。

我当前的代码是:

View.OnTouchListener customPopUpTouchListenr = new View.OnTouchListener(){

    @Override
    public boolean onTouch(View arg0, MotionEvent arg1) {
        Log.d("POPUP", "Touch false");
        return false;
    }

};


LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layout= (LinearLayout)inflater.inflate(R.layout.insert_point_dialog, null);
PopupWindow pw = new PopupWindow(layout,400,200,true);
pw.setOutsideTouchable(true);
pw.setTouchable(true);
pw.setBackgroundDrawable(new BitmapDrawable());
pw.setTouchInterceptor(customPopUpTouchListenr);
pw.showAtLocation(frameLayout, Gravity.BOTTOM, 0, 0);

我的总体目标是创建一个浮动窗口,其行为类似于 gimp 等软件中的“工具调色板”:内部有一些控件,保持在顶部直到被“X”按钮关闭,并允许与外部控件交互它.. 也许有一些更好的方法可以做到这一点,而不是 PopupWindow?但是我还没有找到更合适的控件。

【问题讨论】:

    标签: android


    【解决方案1】:

    这里或其他地方似乎没有任何建议对我有用。所以我这样做了:

    popupWindow.setTouchInterceptor(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    if (motionEvent.getX() < 0 || motionEvent.getX() > viewWidth) return true;
                    if (motionEvent.getY() < 0 || motionEvent.getY() > viewHight) return true;
    
                    return false;
                }
            });
    

    如果触摸在 popupWIdow 的范围内,则不消耗触摸事件(因此按钮或滚动视图将起作用)。如果触摸超出边界,则触摸被消耗,并且不会传递给 popupWindow,因此不会被关闭。

    【讨论】:

    • 谢谢!所有其他答案要么锁定了我的 EditText 字段,要么使弹出窗口透明,或者允许点击外部以关闭我的弹出窗口!
    【解决方案2】:

    只需删除pw.setBackgroundDrawable(new BitmapDrawable());

    【讨论】:

      【解决方案3】:

      太晚了,但对于那些在谷歌上搜索这些东西的人来说 只是改变行的顺序

      pw.showAtLocation(frameLayout, Gravity.BOTTOM, 0, 0);
      pw.setOutsideTouchable(true);
      pw.setTouchable(true);
      pw.setBackgroundDrawable(new BitmapDrawable());
      pw.setTouchInterceptor(customPopUpTouchListenr);
      

      而不是

      pw.setOutsideTouchable(true);
      pw.setTouchable(true);
      pw.setBackgroundDrawable(new BitmapDrawable());
      pw.setTouchInterceptor(customPopUpTouchListenr);
      pw.showAtLocation(frameLayout, Gravity.BOTTOM, 0, 0);
      

      在 showatlocation 方法之后放置任何东西都会让它像什么都没有

      【讨论】:

        【解决方案4】:

        pw.setOutsideTouchable(false);

        【讨论】:

        • 不一样的(点击外面后弹出消失,进入onTouch)。如此处所述:link 可聚焦弹出窗口忽略 setOutsideTouchable 设置。
        【解决方案5】:

        试试

        pw.setBackgroundDrawable(null);
        

        【讨论】:

          【解决方案6】:

          解决办法是:

          popupWindow.setFocusable(true);

          popupWindow.update();

          感谢: http://android-er.blogspot.ch/2012/04/disable-outside-popupwindow-by.html

          【讨论】:

            【解决方案7】:

            首先你要弄清楚为什么popupWindow在你触摸外面的时候会消失。

            看完PopupWindow的源码和资源文件styles.xml,

            <style name="Widget.PopupWindow">
                <item name="popupBackground">@drawable/editbox_dropdown_background_dark</item>
                <item name="popupAnimationStyle">@style/Animation.PopupWindow</item>
            </style>
             <style name="Widget">
                <item name="textAppearance">?textAppearance</item>
            </style>
            

            所以没有什么像对话主题:

            <style name="Theme.Dialog">
            <item name="windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item>
            </style name="Theme.Dialog">
            

            但是当调用 PopupWindow.setBackgroundDrawable() 时会发生一些事情,

            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.setBackgroundDrawable(mBackground);
                    popupViewContainer.addView(mContentView, listParams);
            
                    mPopupView = popupViewContainer;
                } else {
                    mPopupView = mContentView;
                }
                mPopupViewInitialLayoutDirectionInherited =
                        (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
                mPopupWidth = p.width;
                mPopupHeight = p.height;
            }
            

            创建了一个容器视图“PopupViewContainer”。

             private class PopupViewContainer extends FrameLayout {
                private static final String TAG = "PopupWindow.PopupViewContainer";
            
                public PopupViewContainer(Context context) {
                    super(context);
                }
            
            
                @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) {
                    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);
                    }
                }
            

            }

            现在您知道重要的原因了。 因此,有两种方法可以在触摸外部后避免 PopupWindow 关闭。 1.就像@Raaga 所做的那样。删除 pw.setBackgroundDrawable(new BitmapDrawable());

            1. 您可以实现 OnTouchListener 来过滤 PopupWindow 之外的触摸事件。

            【讨论】:

              【解决方案8】:

              有一个非常规的解决方案:覆盖dismiss来阻止它调用super.dismiss,然后创建一个自定义方法来控制dismiss,这里是示例代码:

              class XXXPopupWindow(
                  val context: Context
              ) : PopupWindow() {
              
                  init {
                      //some your own logic initialization
                      isOutsideTouchable = true
                      isTouchable = true
                      isFocusable = false
                  }
              
              
                  override fun dismiss() {
                      //avoid click outside will dismiss this, use close when want to dismiss this
                  }
              
                  fun close(){
                      super.dismiss()
                  }
              }
              

              【讨论】:

                【解决方案9】:

                PopupWindow 可以设置Focusable(false)

                按钮仍然是可点击的,但没有视觉点击行为(一些自定义处理程序强制显示点击?)

                以下是带有“始终在顶部”选项的浮动窗口示例

                浮动窗口附近的原始布局在这两种情况下都是完全可操作的,此外,当窗口仍处于浮动状态时,可以使用对话框和其他弹出窗口

                窗口也是可重复使用的

                final static int buttonAlpha = 0xDF;
                final static float buttonTextSize = 12f;
                
                public final void addPopupButton(LinearLayout linearLayout, String title, android.view.View.OnClickListener onClickListener)
                {
                    Button button = new Button(this.getContext());
                    button.setText(title);
                    button.setTextSize(buttonTextSize);
                    button.getBackground().setAlpha(buttonAlpha);
                    button.setOnClickListener(onClickListener);
                    linearLayout.addView(button, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
                }
                
                public final Button addPopupCheckbox(LinearLayout linearLayout, String title, boolean isChecked, android.view.View.OnClickListener onClickListener)
                {
                    final Button button = new Button(getContext());
                    button.setText(title);
                    button.setTextSize(buttonTextSize);
                    final int buttonHeight = button.getHeight();
                    setButtonChecked(button, isChecked);
                    button.setHeight(buttonHeight);
                    button.getBackground().setAlpha(buttonAlpha);
                    button.setOnClickListener(onClickListener);
                    linearLayout.addView(button, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
                    return button;
                }
                
                public final void setButtonChecked(Button button, boolean isChecked)
                {
                    button.setCompoundDrawablesWithIntrinsicBounds(Resources.getSystem().getIdentifier(isChecked ? "android:drawable/btn_check_on" : "android:drawable/btn_check_off", null, null), 0, 0, 0);
                }
                
                private boolean isMenuAlwaysOnTop = true;
                private PopupWindow popupWindowMenuV2 = null;
                
                public final void popupMenuNav2()
                {
                    if (popupWindowMenuV2 == null)
                    {
                        // [start] layout
                
                        ScrollView scrollView = new ScrollView(this.getContext());
                
                        final LinearLayout linearLayoutNavigation = new LinearLayout(this.getContext());
                        linearLayoutNavigation.setOrientation(LinearLayout.VERTICAL);
                        linearLayoutNavigation.setBackgroundColor(0x7FFFFFFF);
                        linearLayoutNavigation.setPadding(20, 10, 20, 10);
                
                        scrollView.addView(linearLayoutNavigation, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
                
                        popupWindowMenuV2 = new PopupWindow(this);
                        popupWindowMenuV2.setBackgroundDrawable(new BitmapDrawable());
                        popupWindowMenuV2.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
                        popupWindowMenuV2.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
                        popupWindowMenuV2.setTouchable(true);
                        popupWindowMenuV2.setOutsideTouchable(!isMenuAlwaysOnTop);
                        popupWindowMenuV2.setFocusable(!isMenuAlwaysOnTop);
                        popupWindowMenuV2.setTouchInterceptor(new OnTouchListener()
                        {
                            @Override
                            public boolean onTouch(View v, MotionEvent event)
                            {
                                if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_OUTSIDE)
                                {
                                    if (!isMenuAlwaysOnTop)
                                        popupWindowMenuV2.dismiss();
                                    else
                                        return false;
                                    return true;
                                }
                                return false;
                            }
                        });
                        popupWindowMenuV2.setContentView(scrollView);
                
                        // [end] layout
                
                        // [start] always on top checkbox
                
                        final Button buttonMenuAlwaysOnTop = addPopupCheckbox(linearLayoutNavigation, "always on top", isMenuAlwaysOnTop, null);
                        buttonMenuAlwaysOnTop.setOnClickListener(
                                new OnClickListener()
                                {
                                    @Override
                                    public void onClick(View vv)
                                    {
                                        isMenuAlwaysOnTop = !isMenuAlwaysOnTop;
                                        setButtonChecked(buttonMenuAlwaysOnTop, isMenuAlwaysOnTop);
                                        popupWindowMenuV2.dismiss();
                                        popupWindowMenuV2.setOutsideTouchable(!isMenuAlwaysOnTop);
                                        popupWindowMenuV2.setFocusable(!isMenuAlwaysOnTop);
                                        popupWindowMenuV2.showAtLocation(((Activity) getContext()).getWindow().getDecorView(), Gravity.CENTER_VERTICAL + Gravity.RIGHT, 0, 0);
                                    }
                                });
                
                        // [end] always on top checkbox
                
                        addPopupButton(linearLayoutNavigation, "some button",
                                new OnClickListener()
                                {
                                    @Override
                                    public void onClick(View vv)
                                    {
                                        if (!isMenuAlwaysOnTop)
                                            popupWindowMenuV2.dismiss();
                                        someAction();
                                    }
                                });
                
                    }
                
                    popupWindowMenuV2.showAtLocation(((Activity) getContext()).getWindow().getDecorView(), Gravity.CENTER_VERTICAL + Gravity.RIGHT, 0, 0);
                }
                
                // somewhere in handler:
                            if (someCondition)
                            {
                                if (popupWindowMenuV2 != null && popupWindowMenuV2.isShowing())
                                    popupWindowMenuV2.dismiss();
                                else
                                    popupMenuNav2();
                                return true;
                            }
                

                【讨论】:

                  【解决方案10】:

                  popupWindow.setFocusable(false);

                  popupWindow.update();

                  【讨论】:

                  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-21
                  相关资源
                  最近更新 更多