PopupWindow,顾名思义,就是弹窗,在很多场景下都可以见到它,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。

Android 中的弹窗基本有两种,一种是AlertDialog,另一种是PopupWindow**,AlertDialog的显示位置是固定的,PopWindow 的显示位置是我们可以设置和调整的**,因此,像项目中的一些场景如:某个功能的提示说明、点击按钮在按钮上方或者下方弹出菜单、新功能弹窗引导等。由于这些弹窗的位置不固定,因此都可以用PopupWindow来做。

基本用法

使用PopupWindow很简单,可以总结为三个步骤:

1.创建PopupWindow对象实例;
2.设置背景、注册事件监听器和添加动画;
3.显示PopupWindow。
其中,第二步是可选的(不过基本上都要进行第二步的设置)。

案例:

// 用于PopupWindow的View
        View popupView = View.inflate(this, R.layout.popup_window_layout, null);
        //创建PopupWindow
        PopupWindow popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        // 设置 当前获取到焦点(里面的控件才能被点击)
        popupWindow.setFocusable(true);
        // 设置PopupWindow的背景
        popupView.setBackgroundResource(R.color.colorAccent);
        //popupView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        // 设置点击边上消失(要先设置背景样式才有效果),设置为true则点击pop外会消失,设置为false则不会消失。
        // 如果设置了popupWindow.setFocusable(true),则设置为false也会失效
        popupWindow.setOutsideTouchable(true);
        //显示在按钮下方
        popupWindow.showAsDropDown(mButton);

显示在屏幕中央:

popupWindow.showAtLocation(this.getWindow().getDecorView(), Gravity.CENTER, 0, 0);

显示:

//直接显示在参照View 的左下方
public void showAsDropDown(View anchor)
// 显示在参照View的左下方,可以通过xoff,yOff,来调节x,y方向的偏移
public void showAsDropDown(View anchor, int xoff, int off)

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
//显示在指定位置,相对于整个屏幕的window而言,通过gravity调解显示在左、上、右、下、中. x,y调整两个方向的偏移
public void showAtLocation(View parent, int gravity, int x, int y)

使用setAnimationStyle方法添加动画:

这里的动画是指PopupWindow出现和消失时的动画。默认是直接弹出和消失,这样难免让用户有一种突兀的感觉;如果PopupWindow能够“滑入”屏幕和“滑出”屏幕(或者其他方式),用户体验会更好。

为PopupWindow添加动画可以调用setAnimationStyle方法,该方法只有一个参数,就是指定动画的样式,因此我们需要定义动画资源和样式资源。

下面是一个“滑入滑出”动画:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromYDelta="100%p"
        android:toYDelta="0"
        android:duration="300"/>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromYDelta="0"
        android:toYDelta="100%p"
        android:duration="300"/>
</set>
<!-- res/values/styles.xml -->
     <style name="animTranslate">
         <item name="android:windowEnterAnimation">@anim/translate_in</item>
         <item name="android:windowExitAnimation">@anim/translate_out</item>
     </style>

现在PopupWindow的出现/消失已经不是那么突兀了。不过,当弹窗出现后,发现弹窗和背景不是很容易区分,如果此时弹窗的背景能“变暗”就好了。

没问题,我们可以在弹窗出现后让背景变暗,并在弹窗消失后让背景还原:

popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                WindowManager.LayoutParams lp=getWindow().getAttributes();
                lp.alpha=1.0f;
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
                getWindow().setAttributes(lp);
            }
        });

        WindowManager.LayoutParams lp=getWindow().getAttributes();
        lp.alpha=0.3f;
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        getWindow().setAttributes(lp);

完整代码

public class MyPopupWindow extends PopupWindow {

    private Context mContext;
    private PopupWindow mPopupWindow;
    private Window window;

    private int width = ViewGroup.LayoutParams.WRAP_CONTENT;
    private int height = ViewGroup.LayoutParams.WRAP_CONTENT;


    public MyPopupWindow(Context context) {
        mContext = context;
        init();
    }

    public MyPopupWindow(Context context, int width, int height) {
        mContext = context;
        this.width = width;
        this.height = height;
        Activity activity = (Activity) mContext;
        window = activity.getWindow();
        init();
    }

    private void init() {
        View popupView = LayoutInflater.from(mContext).inflate(R.layout.popup_window_layout, null, false);

        //mPopupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        mPopupWindow = new PopupWindow(popupView, width, height);

      /*  //设置宽度
        setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        //设置高度为屏幕高度3/5
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        Point point = new Point();
        windowManager.getDefaultDisplay().getSize(point);
        int windowH = point.y;
        setHeight((windowH * 3) / 5);*/

        //设置当前获取到焦点(里面的控件才能被点击)
        mPopupWindow.setFocusable(true);

        //设置PopupWindow的背景
        popupView.setBackgroundResource(R.color.colorAccent);
        //popupView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

        // 设置点击边上消失(要先设置背景样式才有效果),设置为true则点击pop外会消失,设置为false则不会消失。
        // 如果设置了popupWindow.setFocusable(true),则设置为false也会失效
        mPopupWindow.setOutsideTouchable(true);

        //设置动画
        mPopupWindow.setAnimationStyle(R.style.animTranslate);

        //让背景变暗
        //setAlpha(0.3f);
    }


    /**
     * 显示在控件的下方
     *
     * @param view
     */
    public void show(View view) {
        if (!mPopupWindow.isShowing()) {
            mPopupWindow.showAsDropDown(view);
        }
    }

    /**
     * 显示在参照View的左下方,可以通过xoff,yOff,来调节x,y方向的偏移
     *
     * @param view
     * @param xoff
     * @param yoff
     */
    public void show(View view, int xoff, int yoff) {
        if (!mPopupWindow.isShowing()) {
            mPopupWindow.showAsDropDown(view, xoff, yoff);
        }
    }

    /**
     * 显示在屏幕中央
     */
    public void showCenter() {
        //显示在指定位置,相对于整个屏幕的window而言,通过gravity调解显示在左、上、右、下、中. x,y调整两个方向的偏移
        if (!mPopupWindow.isShowing()) {
            mPopupWindow.showAtLocation(window.getDecorView(), Gravity.CENTER, 0, 0);
        }
    }

    /**
     * 让背景变暗
     *
     * @param alpha
     */
    public void setAlpha(float alpha) {
        mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                WindowManager.LayoutParams lp = window.getAttributes();
                lp.alpha = 1.0f;
                window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
                window.setAttributes(lp);
            }
        });

        WindowManager.LayoutParams lp = window.getAttributes();
        lp.alpha = alpha;
        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.setAttributes(lp);
    }
}

PopupWindow常用方法

设置内容View

setContentView(View contentView)

除了正常在实例化PopupWindow的时候直接将view传入也可以用这个方法在实例化后重新配置需要的view

设置PopupWindow宽度与高度

setWidth(int width)

setHeight(int height)

除了正常实例化的时候传入宽度与高度,也可以用这个2个方法在实例化后在重新配置需要的宽度与高度

设置PopupWindow背景

setBackgroundDrawable(Drawable background)

设置外部点击退出

setOutsideTouchable(boolean touchable)

设置PopupWindow可聚焦

setFocusable(boolean focusable)

除了一般的聚焦选中功能,还有一个用处重点!重点!重点!设置了可聚焦后,返回back键按下后,不会直接退出当前activity而是先退出当前的PopupWindow.

设置弹窗弹出的动画高度

setElevation(float elevation)

原本没有设置,弹窗的弹出动画效果位置就只会在控件附件,但是设置后弹窗的起始动画位置就变更远了

PopupWindow的封装:

package com.xiaoyehai.dialogtest;

import android.content.Context;
import android.view.View;
import android.widget.PopupWindow;

/**
 * Created by xiaoyehai on 2018/6/15 0015.
 */

public class CommonPopupWindow extends PopupWindow {

    final PopupController controller;

    @Override
    public int getWidth() {
        return controller.mPopupView.getMeasuredWidth();
    }

    @Override
    public int getHeight() {
        return controller.mPopupView.getMeasuredHeight();
    }

    public interface ViewInterface {
        void getChildView(View view, int layoutResId);
    }

    private CommonPopupWindow(Context context) {
        controller = new PopupController(context, this);
    }

    @Override
    public void dismiss() {
        super.dismiss();
        controller.setBackGroundLevel(1.0f);
    }

    public static class Builder {
        private final PopupController.PopupParams params;
        private ViewInterface listener;

        public Builder(Context context) {
            params = new PopupController.PopupParams(context);
        }

        /**
         * @param layoutResId 设置PopupWindow 布局ID
         * @return Builder
         */
        public Builder setView(int layoutResId) {
            params.mView = null;
            params.layoutResId = layoutResId;
            return this;
        }

        /**
         * @param view 设置PopupWindow布局
         * @return Builder
         */
        public Builder setView(View view) {
            params.mView = view;
            params.layoutResId = 0;
            return this;
        }

        /**
         * 设置子View
         *
         * @param listener ViewInterface
         * @return Builder
         */
        public Builder setViewOnclickListener(ViewInterface listener) {
            this.listener = listener;
            return this;
        }

        /**
         * 设置宽度和高度 如果不设置 默认是wrap_content
         *
         * @param width 宽
         * @return Builder
         */
        public Builder setWidthAndHeight(int width, int height) {
            params.mWidth = width;
            params.mHeight = height;
            return this;
        }

        /**
         * 设置背景灰色程度
         *
         * @param level 0.0f-1.0f
         * @return Builder
         */
        public Builder setBackGroundLevel(float level) {
            params.isShowBg = true;
            params.bg_level = level;
            return this;
        }

        /**
         * 是否可点击Outside消失
         *
         * @param touchable 是否可点击
         * @return Builder
         */
        public Builder setOutsideTouchable(boolean touchable) {
            params.isTouchable = touchable;
            return this;
        }

        /**
         * 设置动画
         *
         * @return Builder
         */
        public Builder setAnimationStyle(int animationStyle) {
            params.isShowAnim = true;
            params.animationStyle = animationStyle;
            return this;
        }

        public CommonPopupWindow create() {
            final CommonPopupWindow popupWindow = new CommonPopupWindow(params.mContext);
            params.apply(popupWindow.controller);
            if (listener != null && params.layoutResId != 0) {
                listener.getChildView(popupWindow.controller.mPopupView, params.layoutResId);
            }
           measureWidthAndHeight(popupWindow.controller.mPopupView);
            return popupWindow;
        }

        private void measureWidthAndHeight(View popupView) {
            //设置测量模式为UNSPECIFIED可以确保测量不受父View的影响
            int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            popupView.measure(w, h);
            //得到测量宽度
            int mWidth=popupView.getMeasuredWidth();
            //得到测量高度
            int mHeight=popupView.getMeasuredHeight();

        }
    }
}
package com.xiaoyehai.dialogtest;

import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.PopupWindow;

/**
 * Created by xiaoyehai on 2018/6/15 0015.
 */

public class PopupController {

    private int layoutResId;//布局id
    private Context context;
    private PopupWindow popupWindow;
    View mPopupView;//弹窗布局View
    private View mView;
    private Window mWindow;

    PopupController(Context context, PopupWindow popupWindow) {
        this.context = context;
        this.popupWindow = popupWindow;
    }

    public void setView(int layoutResId) {
        mView = null;
        this.layoutResId = layoutResId;
        installContent();
    }

    public void setView(View view) {
        mView = view;
        this.layoutResId = 0;
        installContent();
    }

    private void installContent() {
        if (layoutResId != 0) {
            mPopupView = LayoutInflater.from(context).inflate(layoutResId, null);
        } else if (mView != null) {
            mPopupView = mView;
        }
        popupWindow.setContentView(mPopupView);
    }

    /**
     * 设置宽度
     *
     * @param width  宽
     * @param height 高
     */
    private void setWidthAndHeight(int width, int height) {
        if (width == 0 || height == 0) {
            //如果没设置宽高,默认是WRAP_CONTENT
            popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
            popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        } else {
            popupWindow.setWidth(width);
            popupWindow.setHeight(height);
        }
    }


    /**
     * 设置背景灰色程度
     *
     * @param level 0.0f-1.0f
     */
    void setBackGroundLevel(float level) {
        mWindow = ((Activity) context).getWindow();
        WindowManager.LayoutParams params = mWindow.getAttributes();
        params.alpha = level;
        mWindow.setAttributes(params);
    }


    /**
     * 设置动画
     */
    private void setAnimationStyle(int animationStyle) {
        popupWindow.setAnimationStyle(animationStyle);
    }

    /**
     * 设置Outside是否可点击
     *
     * @param touchable 是否可点击
     */
    private void setOutsideTouchable(boolean touchable) {
        popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));//设置透明背景
        popupWindow.setOutsideTouchable(touchable);//设置outside可点击
        popupWindow.setFocusable(touchable);
    }


    static class PopupParams {
        public int layoutResId;//布局id
        public Context mContext;
        public int mWidth, mHeight;//弹窗的宽和高
        public boolean isShowBg, isShowAnim;
        public float bg_level;//屏幕背景灰色程度
        public int animationStyle;//动画Id
        public View mView;
        public boolean isTouchable = true;

        public PopupParams(Context mContext) {
            this.mContext = mContext;
        }

        public void apply(PopupController controller) {
            if (mView != null) {
                controller.setView(mView);
            } else if (layoutResId != 0) {
                controller.setView(layoutResId);
            } else {
                throw new IllegalArgumentException("PopupView's contentView is null");
            }
            controller.setWidthAndHeight(mWidth, mHeight);
            controller.setOutsideTouchable(isTouchable);//设置outside可点击
            if (isShowBg) {
                //设置背景
                controller.setBackGroundLevel(bg_level);
            }
            if (isShowAnim) {
                controller.setAnimationStyle(animationStyle);
            }
        }
    }

}

使用:

CommonPopupWindow popupWindow = new CommonPopupWindow.Builder(this)
                .setView(R.layout.popup_window_layout)
                .setWidthAndHeight(400, 400)
                //设置动画
                .setAnimationStyle(R.style.AnimHorizontal)
                //设置背景颜色,取值范围0.0f-1.0f 值越小越暗 1.0f为透明
                .setBackGroundLevel(0.5f)
                //设置PopupWindow里的子View及点击事件
                .setViewOnclickListener(new CommonPopupWindow.ViewInterface() {
                    @Override
                    public void getChildView(View view, int layoutResId) {
					TextView tv_child = (TextView) view.findViewById(R.id.tv_child);
	                tv_child.setText("我是子View");
                    }
                })
                .setOutsideTouchable(true)
                .create();

        popupWindow.showAtLocation(this.getWindow().getDecorView(), Gravity.CENTER, 0, 0);