当Android PopupWindow处于活动状态时,模糊或变暗背景。

92

我希望在使用popup.showAtLocation显示弹出窗口时,能够使背景模糊或变暗,并在调用popup.dismiss时取消背景的模糊或变暗。

我尝试将布局参数FLAG_BLUR_BEHINDFLAG_DIM_BEHIND应用于我的活动,但这似乎会在启动应用程序时立即使背景变模糊和变暗。

如何只对弹出窗口进行模糊/变暗处理?


1
警告:FLAG_BLUR_BEHIND 在某些手机上存在缺陷,并且会使手机非常不响应(显然模糊是在软件中完成的)。例如,在Droid上。除此之外,像Macarse所说的那样 - FLAG_BLUR_BEHIND 需要应用于前景窗口。 - EboMike
可能是在屏幕上显示编辑文本的最佳方法?的重复问题。 - Pops
1
我正在寻找这个问题的答案。有没有办法通过编程来调暗弹出窗口后面的视图? - Nick
1
@Nick 你是如何解决这个问题的? - Amit
2021年完美解决方案:https://github.com/sergei-lapin/BlurView - Fattie
12个回答

113
问题是关于 Popupwindow 类的,但是每个人都给出了使用 Dialog 类的答案。如果你需要使用 Popupwindow 类,这几乎没有任何用处,因为 Popupwindow 没有一个 getWindow() 方法。
我找到了一种实际上可以与 Popupwindow 一起使用的解决方案。它只需要将用作背景活动的xml文件的根设置为 FrameLayout。您可以给Framelayout元素添加一个android:foreground标记。这个标记指定了一个可绘制的资源,它将覆盖整个活动(也就是说,如果Framelayout是xml文件中的根元素)。然后,您可以控制前景可绘制的不透明度(setAlpha())。
您可以使用任何您喜欢的可绘制资源,但如果您只想要一个变暗的效果,请在drawable文件夹中创建一个以<shape>标记作为根的xml文件。
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="#000000" />
</shape>

请参考http://developer.android.com/guide/topics/resources/drawable-resource.html#Shape获取更多关于shape元素的信息。

请注意,我在颜色标签中没有指定alpha值,这会使绘画项变为透明的(例如:#ff000000)。这是因为任何硬编码的alpha值都会覆盖我们通过代码中的setAlpha()所设置的任何新的alpha值,所以我们不想要那样。然而,这意味着绘画项最初将是不透明的(实心、不透明的)。因此,我们需要在活动的onCreate()方法中使其透明。

以下是Framelayout xml元素代码:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainmenu"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:foreground="@drawable/shape_window_dim" >
...
... your activity's content
...
</FrameLayout>

这是Activity的onCreate()方法:

public void onCreate( Bundle savedInstanceState)
{
  super.onCreate( savedInstanceState);

  setContentView( R.layout.activity_mainmenu);

  //
  // Your own Activity initialization code
  //

  layout_MainMenu = (FrameLayout) findViewById( R.id.mainmenu);
  layout_MainMenu.getForeground().setAlpha( 0);
}

最后,将活动界面变暗的代码:

layout_MainMenu.getForeground().setAlpha( 220); // dim

layout_MainMenu.getForeground().setAlpha( 0); // restore
alpha值从0(不透明)到255(透明)。 当你关闭Popupwindow时,应该取消变暗的活动。
我没有包含展示和关闭Popupwindow的代码,但这里有一个链接,可以告诉你如何实现: http://www.mobilemancer.com/2011/01/08/popup-window-in-android/

为了确保这在旧设备上也能正常工作,您必须在更改其 alpha 通道后使前景失效。我已将此添加到答案中。对于这个好答案加一。 - user2224350
1
我想不出任何理由为什么这不被标记为正确答案... 至少可以说是非常棒的答案。 - Jayshil Dave
2
在视图中使用了Actionbar,你不能使其变暗。 - BaDo
这对我来说效果最好...下面的双重弹出框存在竞争条件,因此有时会出现暗淡的弹出框在真正的弹出框之上。 - kenyee
1
几乎完美。唯一的缺点是你必须使用FrameLayout,因为其他布局没有“getForeground”方法。 - LiangWang

91

由于 PopupWindow 仅将 View 添加到 WindowManager,您可以使用 updateViewLayout (View view, ViewGroup.LayoutParams params) 在调用 show..() 后更新您的 PopupWindowcontentViewLayoutParams

设置窗口标志 FLAG_DIM_BEHIND 将使窗口后面的所有内容变暗。使用 dimAmount 来控制暗度的程度(1.0 表示完全不透明,0.0 表示不暗)。

请记住,如果您将背景设置为您的 PopupWindow,它会将您的 contentView 放入容器中,这意味着您需要更新其父级。

有背景:

PopupWindow popup = new PopupWindow(contentView, width, height);
popup.setBackgroundDrawable(background);
popup.showAsDropDown(anchor);

View container = (View) popup.getContentView().getParent();
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
// add flag
p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
p.dimAmount = 0.3f;
wm.updateViewLayout(container, p);

没有背景:

PopupWindow popup = new PopupWindow(contentView, width, height);
popup.setBackgroundDrawable(null);
popup.showAsDropDown(anchor);

WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams p = (WindowManager.LayoutParams) contentView.getLayoutParams();
// add flag
p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
p.dimAmount = 0.3f;
wm.updateViewLayout(contentView, p);

棉花糖更新:

在M版本中,PopupWindow将contentView包装在一个名为mDecorView的FrameLayout中。如果您深入研究PopupWindow源代码,您将发现类似于createDecorView(View contentView)的内容。mDecorView的主要目的是处理事件分发和内容转换,这是M版本中新增的功能。这意味着我们需要再添加一个.getParent()来访问容器。

如果需要背景,那么需要进行如下更改:

View container = (View) popup.getContentView().getParent().getParent();

API 18+的更好替代方案

使用ViewGroupOverlay的较少hack的解决方案:

1) 获取所需根布局的引用

ViewGroup root = (ViewGroup) getWindow().getDecorView().getRootView();

2)调用applyDim(root,0.5f);clearDim()

public static void applyDim(@NonNull ViewGroup parent, float dimAmount){
    Drawable dim = new ColorDrawable(Color.BLACK);
    dim.setBounds(0, 0, parent.getWidth(), parent.getHeight());
    dim.setAlpha((int) (255 * dimAmount));

    ViewGroupOverlay overlay = parent.getOverlay();
    overlay.add(dim);
}

public static void clearDim(@NonNull ViewGroup parent) {
    ViewGroupOverlay overlay = parent.getOverlay();
    overlay.clear();
}

2
这在运行Android 6.0 Marshmallow的设备上似乎不再起作用了。layoutParams不能转换为WindowManager.LayoutParams。有什么解决方法吗? - Robert
谢谢指出。我还没有在M上测试过。会尽快更新。 - Markus Rubey
我也遇到了同样的问题。我使用了带有背景的上面的代码。 - Rizwan Sohaib
如果p为null,请使用以下代码:if (p != null) { //相同的代码 } else { popupView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { v.removeOnLayoutChangeListener(this); WindowManager.LayoutParams p = (WindowManager.LayoutParams) v.getLayoutParams(); p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; p.dimAmount = 0.3f; wm.updateViewLayout(v, p); } }); } - Beytan Kurt
1
顺便提一下,当我在较低的API级别(例如15和20)上使用它时,它会崩溃,因为popup.getContentView().getParent()不返回View,所以强制转换会导致崩溃。对于这种情况,我使用了@BeytanKurt的方法作为备选方案。 - Matthew Horst
@MarkusRubey,我需要更改什么来适应Android M?这一行代码()popup.getContentView().getParent().getParent()导致了崩溃。 - Barnali Bhattacharjee

30

在你的xml文件中添加类似以下代码,将宽度和高度设置为'match_parent'。

<RelativeLayout
        android:id="@+id/bac_dim_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#C0000000"
        android:visibility="gone" >
</RelativeLayout>

在您的activity的onCreate方法中

//setting background dim when showing popup
back_dim_layout = (RelativeLayout) findViewById(R.id.share_bac_dim_layout);

最后,在显示弹窗时使其可见,并在退出弹窗时使其不可见。

back_dim_layout.setVisibility(View.VISIBLE);
back_dim_layout.setVisibility(View.GONE);

3
但这不会使操作/工具栏变暗。 - Silvia H
请告诉我如何操作工具栏的暗淡效果。 - SAndroidD
1
在PopupWindow.OnDismissListener中添加back_dim_layout.setVisibility(View.GONE); - Pulkit

12

另一个技巧是使用两个弹出窗口,而不是一个。第一个弹出窗口将只是带有半透明背景的虚拟视图,它提供了暗淡效果。第二个弹出窗口是您预期的弹出窗口。

创建弹出窗口时的顺序: 首先显示虚拟弹出窗口1,然后再显示预期的弹出窗口。

销毁时的顺序: 关闭预期的弹出窗口,然后关闭虚拟弹出窗口。

将这两个窗口链接在一起的最佳方式是添加OnDismissListener并覆盖预期弹出窗口的onDismiss()方法来从中关闭虚拟弹出窗口。

虚拟弹出窗口的代码:

fadepopup.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" 
    android:id="@+id/fadePopup"
    android:background="#AA000000">
</LinearLayout>

显示淡出弹出窗口以变暗背景

private PopupWindow dimBackground() {

    LayoutInflater inflater = (LayoutInflater) EPGGRIDActivity.this
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    final View layout = inflater.inflate(R.layout.fadepopup,
            (ViewGroup) findViewById(R.id.fadePopup));
    PopupWindow fadePopup = new PopupWindow(layout, windowWidth, windowHeight, false);
    fadePopup.showAtLocation(layout, Gravity.NO_GRAVITY, 0, 0);
    return fadePopup;
}

对我来说几乎可以运行...有时候存在一种竞态条件,弹出窗口会在模糊/虚拟弹出窗口的后面。还没有找到解决方法...即使延迟显示弹出窗口以便它在延迟的窗口后面有时间做出响应也没有帮助 :-P - kenyee
2
@kenyee 我已经找到了解决这个问题的方法。我首先显示“淡入”弹出窗口,然后为了显示第二个弹出窗口,我使用了myFadePopup.getContentView().post( ... myPopup.showAtLocation ... ); - Piotr

5

我已经找到了解决方案

创建一个自定义的透明对话框,在该对话框内打开弹出窗口:

dialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
emptyDialog = LayoutInflater.from(context).inflate(R.layout.empty, null);

/* blur background*/
WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();  
lp.dimAmount=0.0f;  
dialog.getWindow().setAttributes(lp);  
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 
dialog.setContentView(emptyDialog);
dialog.setCanceledOnTouchOutside(true);
dialog.setOnShowListener(new OnShowListener()
{
    @Override
    public void onShow(DialogInterface dialogIx)
    {
        mQuickAction.show(emptyDialog); //open the PopupWindow here
    }
});
dialog.show();

对话框的XML(R.layout.empty):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="match_parent" android:layout_width="match_parent"
     style="@android:style/Theme.Translucent.NoTitleBar" />

现在您想要在弹出窗口关闭时关闭对话框。所以

mQuickAction.setOnDismissListener(new OnDismissListener()
{
    @Override
    public void onDismiss()
    {
        if(dialog!=null)
        {
            dialog.dismiss(); // dismiss the empty dialog when the PopupWindow closes
            dialog = null;
        }
    }
});

注意:我在此处使用了NewQuickAction插件来创建弹出窗口。这也可以在本机弹出窗口上完成。

1
findViewById(R.id.drawer_layout).setAlpha((float) 0.7);

R.id.drawer_layout 是您想要调暗亮度的布局的 ID。


1
也许这个代码库对您有帮助:BasePopup 这是我的代码库,用于解决各种弹出窗口的问题。
在使用该库的情况下,如果需要模糊背景,请调用setBlurBackgroundEnable(true)
请参阅维基获取更多详细信息。(语言为zh-cn) BasePopup:wiki

1
对我来说,像Abdelhak Mouaamou的答案一样的东西可以工作,在API级别16和27上进行了测试。
不要使用popupWindow.getContentView().getParent()并将结果转换为View(在API级别16上会崩溃,因为它返回一个ViewRootImpl对象,它不是View的实例),我只是使用.getRootView(),它已经返回一个视图,因此不需要强制转换。
希望能帮助某些人 :)
完整的工作示例从其他stackoverflow帖子中混合在一起,只需将其复制粘贴到按钮的onClick监听器中即可。
// inflate the layout of the popup window
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
if(inflater == null) {
    return;
}
//View popupView = inflater.inflate(R.layout.my_popup_layout, null); // this version gives a warning cause it doesn't like null as argument for the viewRoot, c.f. https://dev59.com/_GAf5IYBdhLWcg3wSQ4z and https://dev59.com/_18d5IYBdhLWcg3w1E50
View popupView = View.inflate(MyParentActivity.this, R.layout.my_popup_layout, null);

// create the popup window
final PopupWindow popupWindow = new PopupWindow(popupView,
        LinearLayout.LayoutParams.WRAP_CONTENT,
        LinearLayout.LayoutParams.WRAP_CONTENT,
        true // lets taps outside the popup also dismiss it
        );

// do something with the stuff in your popup layout, e.g.:
//((TextView)popupView.findViewById(R.id.textview_popup_helloworld))
//      .setText("hello stackoverflow");

// dismiss the popup window when touched
popupView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            popupWindow.dismiss();
            return true;
        }
});

// show the popup window
// which view you pass in doesn't matter, it is only used for the window token
popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0);
//popupWindow.setOutsideTouchable(false); // doesn't seem to change anything for me

View container = popupWindow.getContentView().getRootView();
if(container != null) {
    WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams p = (WindowManager.LayoutParams)container.getLayoutParams();
    p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
    p.dimAmount = 0.3f;
    if(wm != null) {
        wm.updateViewLayout(container, p);
    }
}

0

由于您正在尝试通过模糊背景屏幕来弹出对话框窗口,因此您必须使用以下一组代码行。首先需要获取对话框属性,然后为对话框属性设置一些 alpha 值。

现在,您的带有模糊背景的对话框已经准备好了。但是重要的因素是为窗口设置一个标志FLAG_DIM_BEHIND

现在结果就是您的了。希望对某人有所帮助...

            WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();
            lp.dimAmount=0.6f;
            dialog.getWindow().setAttributes(lp);
            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

0

您可以使用android:theme="@android:style/Theme.Dialog"来实现这一点。 创建一个活动,在您的AndroidManifest.xml中将该活动定义为:

    <activity android:name=".activities.YourActivity"
              android:label="@string/your_activity_label"
              android:theme="@android:style/Theme.Dialog">

这似乎行不通。我将其应用于我的唯一活动,该活动在显示弹出窗口时位于后台,该弹出窗口只是我拥有的一个膨胀布局,而不是单独的活动。如何将FLAG_BLUR_BEHIND应用于PopupWindow? - k_day

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