弹出PopupWindow后让背景变暗的方法

  在Android上使用AlertDialog和PopupWindow都可以很方便的实现弹窗,AlertDialog弹出后背景会变暗,而PopupWindow不具有此特性。StackOVerflow上的这个问题里给出了三种方法以实现弹出PopupWindow后让背景变暗的效果,整理如下。

1. 方法一

  使用WindowManager.LayoutParams.FLAG_DIM_BEHIND可以实现变暗的效果。

private void dimBackground(PopupWindow popup, float dimAmount) {
    ViewParent vp = popup.getContentView().getParent();
    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    if (vp instanceof View) {
        // PopupWindow has background drawable set
        View container = (View) popup.getContentView().getParent();
        WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();
        p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        p.dimAmount = dimAmount;
        wm.updateViewLayout(container, p);
    } else {
        // PopupWindow has no background drawable
        WindowManager.LayoutParams p =
                (WindowManager.LayoutParams) popup.getContentView().getLayoutParams();
        p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        p.dimAmount = dimAmount;
        wm.updateViewLayout(popup.getContentView(), p);
    }
}

2. 方法二

  为 RootView 的 ViewGroupOverlay 添加黑色 Drawable 并修改其透明度,也可以实现让整个背景窗口的效果。只适用于 API Level 18 及以上。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public void applyDim(float dimAmount){
    ViewGroup parent = (ViewGroup) getWindow().getDecorView().getRootView();
    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);
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public void clearDim() {
    ViewGroup parent = (ViewGroup) getWindow().getDecorView().getRootView();
    ViewGroupOverlay overlay = parent.getOverlay();
    overlay.clear();
}

3. 方法三

  使用 FrameLayout 作为背景布局的 Root,并设置 FrameLayout 的 foreground 为黑色不透明的 shape,之后就可以通过修改 shape 的透明度,让整个窗口变暗。

  所使用的的 shape (/drawable/dim.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>

注意其中颜色为黑色(#000000),没有指定Alpha通道(透明度)的值。如果在这里指定了Alpha通道,如使用#ff000000,该Alpha通道的值(0xff)就会固定,无法再通过setAlpha()方法设置。

  把要变暗的区域放在 FrameLayout 中,并设置 FrameLayout 的 foreground 为上面定义的 dim.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:foreground="@drawable/dim"
    tools:foreground="">

    ...

</FrameLayout>

由于 dim.xml 是不透明的黑色,这里还设置了tools:foreground="",以免在Preview窗口一团黑。

  同样由于 dim.xml 是黑色的,在初始化时记得首先把它设置为透明:

mContainer = (FrameLayout) findViewById(R.id.container);
mContainer.getForeground().setAlpha(0);

  然后就可以通过setAlpha()让 FrameLayout 区域变暗或恢复:

mContainer.getForeground().setAlpha(127);
mContainer.getForeground().setAlpha(0);

  这种方法虽然麻烦,但适用性最广。

4. 完整代码

  完整代码可以在这里找到。