美文网首页优秀案例
Android 自定义PopupWindow

Android 自定义PopupWindow

作者: 听话哥 | 来源:发表于2018-05-07 17:12 被阅读59次

基于之前的业务有一些弹窗的需求,这个弹窗需要在多个业务场景显示,所以选择了PopupWindow来实现相关业务。至于为何要选择PopupWindow 这个主要还是取决于自己的业务和使用场景,具体PopupWindow和Dialog的区别可以去参考:Dialog和PopUpWindow的抉择 这里讲的挺详细的。

自定义PopupWindow

我们大部分的需求都是需要弹窗内容在底部显示,也有一些需求会根据控件位置显示,首先先定义PopupWindow 控件类:


public class CustomPopupWindow extends PopupWindow implements View.OnClickListener{


    private View mView;
    private TextView mTvTips;

    @IntDef({
            VerticalPosition.CENTER,
            VerticalPosition.ABOVE,
            VerticalPosition.BELOW,
            VerticalPosition.ALIGN_TOP,
            VerticalPosition.ALIGN_BOTTOM,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface VerticalPosition {
        int CENTER = 0;
        int ABOVE = 1;
        int BELOW = 2;
        int ALIGN_TOP = 3;
        int ALIGN_BOTTOM = 4;
    }

    @IntDef({
            HorizontalPosition.CENTER,
            HorizontalPosition.LEFT,
            HorizontalPosition.RIGHT,
            HorizontalPosition.ALIGN_LEFT,
            HorizontalPosition.ALIGN_RIGHT,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface HorizontalPosition {
        int CENTER = 0;
        int LEFT = 1;
        int RIGHT = 2;
        int ALIGN_LEFT = 3;
        int ALIGN_RIGHT = 4;
    }

    public CustomPopupWindow(Context context) {
        init(context);
    }

    private void init(Context context) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mView = inflater.inflate(R.layout.popupwindow_layout, null);
        mTvTips = mView.findViewById(R.id.tv_popupwindow_tips);
        mTvTips.setOnClickListener(this);
        // 设置外部可点击
        this.setOutsideTouchable(false);

        /* 设置弹出窗口特征 */
        // 设置视图
        this.setContentView(this.mView);
        // 设置弹出窗体的宽和高
        this.setHeight(RelativeLayout.LayoutParams.WRAP_CONTENT);
        this.setWidth(RelativeLayout.LayoutParams.WRAP_CONTENT);

        // 设置弹出窗体可点击
        this.setFocusable(true);

        // 实例化一个ColorDrawable颜色为半透明
        ColorDrawable dw = new ColorDrawable(0x10000000);
        // 设置弹出窗体的背景
        this.setBackgroundDrawable(dw);

        // 设置弹出窗体显示时的动画,从底部向上弹出
        this.setAnimationStyle(R.style.shop_popup_window_anim);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.tv_popupwindow_tips:
                if (mListener != null){
                    mListener.onClick();
                }
                break;
            default:
                break;
        }
    }

    public void setOnItemClickListener(onItemClickListener listener) {
        mListener = listener;
    }

    private onItemClickListener mListener;

    public interface onItemClickListener {
        void onClick();
    }

    WindowManager.LayoutParams params;
    public void showBottom(Activity activity){
        showAtLocation(activity.getWindow().getDecorView().findViewById(android.R.id.content), Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
        params = activity.getWindow().getAttributes();

        params.alpha = 0.7f;
        activity.getWindow().setAttributes(params);
    }

    public void showPopupWindow(Activity activity, @NonNull View anchor, @VerticalPosition int vertPos,
                                @HorizontalPosition int horizPos, int x, int y, boolean fitInScreen){
        setClippingEnabled(fitInScreen);
        View contentView = getContentView();
        contentView.measure(makeDropDownMeasureSpec(getWidth()), makeDropDownMeasureSpec(getHeight()));
        final int measuredW = contentView.getMeasuredWidth();
        final int measuredH = contentView.getMeasuredHeight();
        if (!fitInScreen) {
            final int[] anchorLocation = new int[2];
            anchor.getLocationInWindow(anchorLocation);
            x += anchorLocation[0];
            y += anchorLocation[1] + anchor.getHeight();
        }
        switch (vertPos) {
            case VerticalPosition.ABOVE:
                y -= measuredH + anchor.getHeight();
                break;
            case VerticalPosition.ALIGN_BOTTOM:
                y -= measuredH;
                break;
            case VerticalPosition.CENTER:
                y -= anchor.getHeight()/2 + measuredH/2;
                break;
            case VerticalPosition.ALIGN_TOP:
                y -= anchor.getHeight();
                break;
            case VerticalPosition.BELOW:
                // Default position.
                break;
        }
        switch (horizPos) {
            case HorizontalPosition.LEFT:
                x -= measuredW;
                break;
            case HorizontalPosition.ALIGN_RIGHT:
                x -= measuredW - anchor.getWidth();
                break;
            case HorizontalPosition.CENTER:
                x += anchor.getWidth()/2 - measuredW/2;
                break;
            case HorizontalPosition.ALIGN_LEFT:
                // Default position.
                break;
            case HorizontalPosition.RIGHT:
                x += anchor.getWidth();
                break;
        }
        if (fitInScreen) {
            PopupWindowCompat.showAsDropDown(this, anchor, x, y, Gravity.NO_GRAVITY);
        } else {
            showAtLocation(anchor, Gravity.NO_GRAVITY, x, y);
        }
        params = activity.getWindow().getAttributes();

        params.alpha = 0.7f;
        activity.getWindow().setAttributes(params);
    }

    @SuppressWarnings("ResourceType")
    private static int makeDropDownMeasureSpec(int measureSpec) {
        return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), getDropDownMeasureSpecMode(measureSpec));
    }

    private static int getDropDownMeasureSpecMode(int measureSpec) {
        switch (measureSpec) {
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                return View.MeasureSpec.UNSPECIFIED;
            default:
                return View.MeasureSpec.EXACTLY;
        }
    }
}

自定义Style

    <style name="shop_popup_window_anim" parent="android:Animation">
        <item name="android:windowEnterAnimation">@anim/pop_enter_anim</item>
        <item name="android:windowExitAnimation">@anim/pop_exit_anim</item>
    </style>

自定义进入进出动画

pop_enter_anim

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:duration="200"
        android:fromYDelta="100%p"
        android:toYDelta="0" />
    <alpha
        android:duration="200"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>

pop_exit_anim

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="200"
        android:fromYDelta="0"
        android:toYDelta="50%p" />
    <alpha
        android:duration="200"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />
</set>

这里代码片段比较长,是由业务代码抽离出来的,也是为了完整展示整个自定义PopupWindow,其中有两个点是值得注意的,就是我们提供展示的方法。

一个是showPopupWindow
这里我们需要几个参数,主要的有弹窗的根控件,根据根控件位置去调整需要展示的位置,具体逻辑可以参考代码。

一个是showBottom
这里我们为了能在不同业务场景中使用而且是在底部显示,通常情况下我们会由第一种方式传入父布局的控件,但是在不同activity中,我们就需要去把activity的最外层布局给find出来再传进来,相对麻烦,我们也很难做到所有的activity根布局都用同一个id,有什么方式是可以最简单快捷的找到activity的根视图呢?
上面的方法其实已经给出了答案
getWindow().getDecorView().findViewById(android.R.id.content)

通过这句代码我们就能找出当前activity的根视图,这时候只需要将PopupWindow根据它的位置来展示就可以了

使用PopupWindow

mCustomPopupWindow.showBottom(mActivity);
or
mCustomPopupWindow.showPopupWindow(mActivity, tvShow, vertPos, horizPos, 0, 0, fitInScreen);

如果需要参考可以参考一下例子
https://github.com/cyihui/CustomPopupWindowDemo/tree/master

这里只是简单的抽离,如果需要使用还可以自行去封装,如果有更好的建议也可以一起探讨一下。

相关文章

网友评论

    本文标题:Android 自定义PopupWindow

    本文链接:https://www.haomeiwen.com/subject/zpdvrftx.html