在Android中使用PopupWindow调暗背景

11

我可以使用 PopupWindow 而非 Dialog 来调暗背景吗?我之所以这样问是因为我正在使用 Dialogs,但是 Dialog 不会显示在点击的项目下方,而使用 PopupWindow,弹出窗口已经在项目下方显示了。

3个回答

42

我使用以下代码,并且它对我很有效。

public static void dimBehind(PopupWindow popupWindow) {
    View container;
    if (popupWindow.getBackground() == null) {
        if (VERSION.SDK_INT >= VERSION_CODES.M){
            container = (View) popupWindow.getContentView().getParent();
        } else {
            container = popupWindow.getContentView();
        }
    } else {
        if (VERSION.SDK_INT >= VERSION_CODES.M) {
            container = (View) popupWindow.getContentView().getParent().getParent();
        } else {
            container = (View) popupWindow.getContentView().getParent();
        }
    }
    Context context = popupWindow.getContentView().getContext();
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
    p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
    p.dimAmount = 0.3f;
    wm.updateViewLayout(container, p);
}

来自这个回答

更新:

fdermishin在下方的回答更好。我已经将其向后测试到API级别19,并且它运行良好。

如果您正在使用Kotlin,则最好将其用作Kotlin扩展:

//Just new a kotlin file(e.g. ComponmentExts),
//copy the following function declaration into it
/**
 * Dim the background when PopupWindow shows
 */
fun PopupWindow.dimBehind() {
    val container = contentView.rootView
    val context = contentView.context
    val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    val p = container.layoutParams as WindowManager.LayoutParams
    p.flags = p.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
    p.dimAmount = 0.3f
    wm.updateViewLayout(container, p)
}

//then use it in other place like this:
popupWindow.dimBehind()


2
视图容器 =(View)popUp.getContentView()。getRootView();这适用于M之前和之后的版本。在Jelly Bean和Marshmallow中进行了检查。 - Sanju
这个在Nougat模拟器中崩溃了:com.android.launcher3 W/OpenGLRenderer: Incorrectly called buildLayer on View: ShortcutAndWidgetContainer, destroying layer...。总之,这段代码让我感到不舒服,那个硬编码的 getParent() 的变量数量看起来并不是很具有未来可靠性。 - HughHughTeotl
{btsdaf} - Junyue Cao

29

看起来我找到了如何摆脱API版本检查的方法。使用container = popupWindow.getContentView().getRootView()解决了这个问题,但我还没有为旧的API进行测试。根据曹俊岳的解决方案进行调整:

public static void dimBehind(PopupWindow popupWindow) {
    View container = popupWindow.getContentView().getRootView();
    Context context = popupWindow.getContentView().getContext();
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
    p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
    p.dimAmount = 0.3f;
    wm.updateViewLayout(container, p);
}

我喜欢这个。它需要在窗口显示后调用,并且运行非常好。作为 PopupWindow 的 Kotlin 扩展进行了适应,效果非常棒。 - dvkch
我喜欢这个,也作为 Kotlin 扩展添加了。 - user3425867
2
已在API级别21及以上进行测试,直至今天最新版本(28),并且在所有版本上都可以正常工作。 - Darwind
非常好的答案!在API级别16上进行了测试,它可以正常工作。 - mindoverflow
有趣的事实:最初我在我的项目中使用Kotlin实现了这个解决方案,但后来将其转换为Java以发布答案,因为当时Kotlin远不如Java流行。 - fdermishin

4
JiaJiaGu的解决方案是可行的,但是弹出窗口会与导航栏重叠,因为它清除了所有其他标志。因此,我在这里进行了一些修改:
public static void dimBehind(PopupWindow popupWindow) {
    View container;
    if (VERSION.SDK_INT >= VERSION_CODES.M){
        container = (View) popupWindow.getContentView().getParent();
    } else {
        container = popupWindow.getContentView();
    } 
    if (popupWindow.getBackground() != null) {
        container = container.getParent()
    }
    Context context = popupWindow.getContentView().getContext();
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
    p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; // add a flag here instead of clear others
    p.dimAmount = 0.3f;
    wm.updateViewLayout(container, p);
}

我发布了一个新答案,因为我无法在JiaJiaGu的答案上进行评论。


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