Android基础篇 PopupWindow弹出位置详解

愿所有人都摆脱冷气,只是向上走,不必听自暴自弃者流的话

一、前言

(1)弹窗选择说明:

按目前来说,我们处理弹窗的时候,Google官方提供了两种选择给我们,分别是PopupWindow和Dialog。今天我们的主角是PopupWindow,Dialog的讲解会在后续的文章中推出。

(2)PopupWindow位置异常说明:

开始正式讲解PopupWindow之前,我想事先说明关于PopupWindow位置异常的一些事情,以帮助大家在解决实际开发中,一些异常事情的问题解惑。

  1. PopupWindow 中的 showAsDropDown(View anchor) 用于在指定锚点View下方显示 PopupWindow

在Android 7.0 (api<=23) 前,此方法的效果没有问题,但是在Android7.x系统之上会出现意外的兼容问题:

情况描述:

情况一:高度 “MATCH_PARENT”

  • 条件:指定的 PopupWindow 的高度为 MATCH_PARENT
  • 调用: 调用showAsDropDown(View anchor) 时,在 7.0 之前,会在锚点 anchor 下边缘到屏幕底部之间显示 PopupWindow
  • 其他调用:在 7.0、7.1 系统上的 PopupWindow 会占据整个屏幕(除状态栏之外)

(SDK >= 24时,PopupWindow高度为 match_parent 时,会占据整个屏幕,需要重新设置。)

情况二:高度“WRAP_CONTENT”

如果指定 PopupWindow 的高度为 WRAP_CONTENT, 调用 showAsDropDown(View anchor) 时,便不会出现兼容性的问题。

情况三:高度 “自定义height”

如果指定 PopupWindow 的高度为自定义的值height,调用 showAsDropDown(View anchor)时, 如果 height > 锚点 anchor 下边缘与屏幕底部的距离, 则还是会出现7.0、7.1上显示异常的问题

解决方案:

if(Build.VERSION.SDK_INT < 24){
Log.e("sdk","小于24")
//mDivider : 你指定的View变量
popupWindow.showAsDropDown(mDivider,1,1)
}else{
//SDK >= 24时,PopupWindow 高度为 match_parent 时,会占据整个屏幕,需要重新设置。
int[] location = new int[2];
mDivider.getLocationOnScreen(location);
int offsetY = location[1] + mDivider.getHeight();
int screenHeight = DensityUtil.getScreenHeight(getActivity());
// 重新设置PopupWindow 的高度
popupWindow.setHeight(screenHeight - offsetY);
popupWindow.showAtLocation(mDivider,Gracity.NOGRAVITY,0,offsetY);
}

二、基本使用

- 新建类继承PopupWindow,创建PopupWindow的样式布局

public class DemoPopupWindow extends PopupWindow {

    public DemoPopupWindow(Context context) {
        super(context);
        setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
        setOutsideTouchable(true);
        setFocusable(true);
        setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        //加载的PopupWindow 的样式布局
        View contentView = LayoutInflater.from(context).inflate(R.layout.popup_test, 
              null, false);
        setContentView(contentView);
    }
}

- 基本使用

DemoPopupWindow mDemoWindow = new DemoPopupWindow(this);
//根据指定View定位
PopupWindowCompat.showAsDropDown(mDemoWindow,mButtom,0,0,Gravity.START);
//或者使用showAsDropDown
mDemoWindow .showAsDropDown(...);
//又或者使用showAtLocation根据屏幕来定位
mDemoWindow .showAtLocation(...);

基于View下方,左偏移 或者 右偏移
Gravity.LEFT(Gravity.START):相对于View左对齐;
Gravity.RIGHT(Gravity.END):相对于View靠右显示。

Gravity.CENTER:在showAsDropDown()中是跟 Gravity.LEFT一样,在showAtLocation()中Gravity.CENTER才有效果

- 弹出位置

解决方法:showAsDropDown()中的另外两个参数:xoff、yoff

一、准备工作:

弹框前,需要得到PopupWindow的大小(也就是PopupWindow中contentView的大小)

  • 由于contentView还未绘制,这时候的width、height都是0
  • 需要通过measure测量出contentView的大小,才能进行计算
    @SuppressWarnings("ResourceType")
    private static int makeDropDownMeasureSpec(int measureSpec) {
        int mode;
        if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mode = View.MeasureSpec.UNSPECIFIED;
        } else {
            mode = View.MeasureSpec.EXACTLY;
        }
        return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
    }

二、测量contentView的大小

DemoPopupWindow mDemoWindow = new DemoPopupWindow (this);
//获取PopupWindow的样式视图
View contentView = mDemoWindow .getContentView();
//需要先测量,PopupWindow还未弹出时,宽高为0
contentView.measure(makeDropDownMeasureSpec(window.getWidth()), 
      makeDropDownMeasureSpec(window.getHeight()));

三、弹窗

测量好PopupWindow大小后,就可以在任意位置弹窗了

弹框的位置无非就是根据PopupWindow以及指定View的大小,计算水平、竖直方向偏移

(1)计算偏移: 水平:居左;竖直:居下
在这里插入图片描述

//代码例子:
int offsetX = -mDemoWindow.getContentView().getMeasuredWidth();
int offsetY = 0;
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.START);

在这里插入图片描述

(2)计算偏移: 水平:居中;竖直:居下
在这里插入图片描述

//代码例子:
offsetX = Math.abs(mDemoWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = 0;
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.START);

在这里插入图片描述

(3)计算偏移:水平:右对齐;竖直:居下
在这里插入图片描述

//代码例子:
offsetX = mButton.getWidth() - mDemoWindow.getContentView().getMeasuredWidth();
offsetY = 0;
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.START);

在这里插入图片描述

(4)计算偏移:水平:居中;竖直:居上
在这里插入图片描述

//代码例子
offsetX = Math.abs(mDemoWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = -(mDemoWindow.getContentView().getMeasuredHeight()+mButton.getHeight());
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.START);

在这里插入图片描述

(5)计算偏移:水平:居左;竖直:居中
在这里插入图片描述

//代码例子
offsetX = -mDemoWindow.getContentView().getMeasuredWidth();
offsetY = -(mDemoWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.START);

在这里插入图片描述

(6)计算偏移:水平:居右;竖直:居中
在这里插入图片描述

//代码例子
offsetX = 0;
offsetY = -(mDemoWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(mDemoWindow, mButton, offsetX, offsetY, Gravity.END);

在这里插入图片描述

四、背景变暗

通过改变Window的透明度来实现背景变暗是常用的一种做法。

在PopupWindow中,先写个修改Window透明度的方法(注意,这边的mContext必须是Activity

    /**
     * 控制窗口背景的不透明度
     */
    private void setWindowBackgroundAlpha(float alpha) {
        if (mContext == null) return;
        if (mContext instanceof Activity) {
            Window window = ((Activity) mContext).getWindow();
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            layoutParams.alpha = alpha;
            window.setAttributes(layoutParams);
        }
    }

然后定义透明度:

private float mAlpha = 1f; //背景灰度  0-1  1表示全透明

最后在PopupWindow show的时候调用以下方法

    /**
     * 窗口显示,窗口背景透明度渐变动画
     */
    private void showBackgroundAnimator() {
        if (mAlpha >= 1f) return;
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f, mAlpha);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float alpha = (float) animation.getAnimatedValue();
                setWindowBackgroundAlpha(alpha);
            }
        });
        animator.setDuration(360);
        animator.start();
    }

通过动画来改变Window达到渐变的效果

五、推荐开源库

使用方式:

popup.showOnAnchor(anchor, VerticalPosition.ABOVE, HorizontalPosition.CENTER, false);
  • EasyPopup:一个功能比较全的库,支持背景变暗,背景不可点击(6.0以上通用)等,而且可以链式调用。
  • 缺点:背景变暗效果只支持 4.2 以上的版本。
private EasyPopup mCirclePop;
circlePop = new EasyPopup(this)
        .setContentView(R.layout.layout_circle_comment)
        .setAnimationStyle(R.style.CirclePopAnim)
    //是否允许点击PopupWindow之外的地方消失
        .setFocusAndOutsideEnable(true)
        .createPopup();
//显示
circlePop.showAtAnchorView(view, VerticalGravity.CENTER, HorizontalGravity.LEFT, 0, 0);

如果最小版本大于等于18的话,直接用- EasyPopup就可以了

六、兼容工具类

介于EasyPopup只适配4.2 以上的版本,而如果项目要适配到4.1。结合上面的提到的两个库,以及背景变暗的方案。改造一下,以下是思路:

 SmartPopupWindow popupWindow= SmartPopupWindow.Builder
     .build(Activity.this, view)
     .setAlpha(0.4f)                   //背景灰度     默认全透明
     .setOutsideTouchDismiss(false)    //点击外部消失  默认true(消失)
     .createPopupWindow();             //创建PopupWindow
 popupWindow.showAtAnchorView(view, VerticalPosition.ABOVE, HorizontalPosition.CENTER);
水平方向参数HorizontalPosition:LEFT 、 RIGHT 、 ALIGN_LEFT 、 ALIGN_RIGHT、 CENTER
竖直方向参数VerticalPosition :ABOVE 、 BELOW、 ALIGN_TOP 、 ALIGN_BOTTOM、 CENTER

项目地址 SmartPopupWindow

七、参考

1、RelativePopupWindow
2、EasyPopup
3、Android弹窗_PopupWindow详解 (挺详细的)
4、在任意位置弹出PopupWindow

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

其子昱舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值