问题
在使用Popwindow进行自定义的过程中,需要设置popwindow的宽高。但是宽高很多时候容易出问题。比如下面的例子。
布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#77000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_margin="12dp"
android:background="@drawable/cz_add_rzh_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:drawableStart="@drawable/cz_icon_add_rzh"
android:drawablePadding="4dp"
android:text="申请注册入驻号"
android:textColor="#ff333333"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
<View
android:id="@+id/close_layout"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</LinearLayout>
使用了传统方法设置宽高
实际显示效果很差,没有内容只有一个黑色透明背景。
解决方案
为了解决这个问题,我写了个工具类。原理就是内部创建一个铺面全屏的FrameLayout。在填充布局的时候作为父布局,来自动计算宽高。在通过其内部的LayoutParam来获取宽高,如果宽高是-1的话,(MATCH_PARENT的值)就替换成屏幕的宽高。在编程实践中Popwindow一般会显示在某个控件的下面,这个时候布局中的高度match_parent对应的就不是手机屏幕的高度,而是需要减去上方空间占用的高度。这个时候就可以传入上方控件,可以自动计算被使用的高度。
优化
加入了顶部view的处理
package com.trs.nmip.common.util.web;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
/**
* @author: zhuguohui
* @date: 2025/3/21
* @description: 用于获取View大小的工具类
*/
public class ViewSizeUtil {
private static FrameLayout fullScreenView;
private static FrameLayout getFullScreenView(Context ctx) {
if (fullScreenView == null) {
fullScreenView = new FrameLayout(ctx);
fullScreenView.measure(getWidthMeasureSpec(ctx), getHeightMeasureSpec(ctx));
fullScreenView.layout(0, 0, getScreenWidth(ctx), getScreenHeight(ctx));
}
return fullScreenView;
}
public static class Size {
public int width;
public int height;
public Size(int width, int height) {
this.width = width;
this.height = height;
}
}
/**
* 填充布局,获取宽高,用于在没有父view的情况下,计算宽高
*
* @param ctx 上下文
* @param layoutId 布局id
* @param topView 在其上面的控件,比如popWindow,如果要显示在某个控件下面,并且当前控件的高度需要铺面全屏的话。
* 那么就会减去topView所占用的高度。
* @return 一个pair对象,第一个元素是view,第二个是宽高
*/
public static Pair<View, Size> inflateViewAndGetSize(Context ctx, @LayoutRes int layoutId, View topView, int yOffset) {
View view = LayoutInflater.from(ctx).inflate(layoutId, getFullScreenView(ctx), false);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
int width = layoutParams.width;
int height = layoutParams.height;
if (width == -1) {
width = getScreenWidth(ctx);
}
if (height == -1) {
height = getScreenHeight(ctx);
//如果是铺满全屏,还要显示在某个控件下面,需要减去这个控件使用的距离
if (topView != null) {
int[] location = new int[2];
topView.getLocationOnScreen(location);
int useHeight = location[1] + topView.getHeight();
height -= useHeight;
}
}
height += yOffset;
return new Pair<>(view, new Size(width, height));
}
private static int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm.widthPixels;
}
private static int getScreenHeight(Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRealMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
return screenHeight;
}
private static int getWidthMeasureSpec(Context ctx) {
return View.MeasureSpec.makeMeasureSpec(getScreenWidth(ctx), View.MeasureSpec.AT_MOST);
}
private static int getHeightMeasureSpec(Context ctx) {
return View.MeasureSpec.makeMeasureSpec(getScreenHeight(ctx), View.MeasureSpec.AT_MOST);
}
}
使用方法如下。
public class ChangeRzhPopWindow extends PopupWindow {
public ChangeRzhPopWindow(Context context,View topView) {
super(context);
Pair<View, ViewSizeUtil.Size> pair = ViewSizeUtil.inflateViewAndGetSize(context, R.layout.change_rzh_pop_window,topView);
setContentView(pair.first);
setWidth(pair.second.width);
setHeight(pair.second.height);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setFocusable(true);
setOutsideTouchable(true);
//因为背景是我们创建的。所以需要我们实现点击背景关闭的功能
pair.first.findViewById(R.id.close_layout).setOnClickListener(v-> dismiss());
}
}
效果如下
红色的地方就是顶部view
遇到的问题
在开发中遇到一个通过代码获取的屏幕高度和手机时间高度不一致的情况,通过AI查询得到这样的结果。测试没问题。特此记录一下。
提取基类
把上面的功能提取一个基类。这样可以方便处理这些问题。这个基类还实现了LifecycleOwner 。更方便使用。
package com.trs.app.gzcz.content_manage.ui.rzh_title_bar.change_rzh_pop_window;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.Pair;
import android.view.View;
import android.widget.PopupWindow;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.trs.news.R;
import com.trs.nmip.common.util.web.ViewSizeUtil;
/**
*
* @author: zhuguohui
* @date: 2025/3/21
* @description: 自动处理宽高的PopupWindow
* 加入了LifeCycleOwner的功能
*/
public class EasySizePopupWindow extends PopupWindow implements LifecycleOwner {
LifecycleRegistry registry=new LifecycleRegistry(this);
{
registry.setCurrentState(Lifecycle.State.INITIALIZED);
}
protected void setContentView(Context context, @LayoutRes int layoutId, View topView,int yOffset){
Pair<View, ViewSizeUtil.Size> pair = ViewSizeUtil.inflateViewAndGetSize(context, layoutId,topView,yOffset);
setContentView(pair.first);
setWidth(pair.second.width);
setHeight(pair.second.height);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setFocusable(true);
setOutsideTouchable(true);
//因为背景是我们创建的。所以需要我们实现点击背景关闭的功能
pair.first.findViewById(R.id.close_layout).setOnClickListener(v-> dismiss());
}
@Override
public void showAsDropDown(View anchor) {
super.showAsDropDown(anchor);
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
super.showAsDropDown(anchor, xoff, yoff);
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
super.showAsDropDown(anchor, xoff, yoff, gravity);
updateLifecycleState();
}
@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, gravity, x, y);
updateLifecycleState();
}
@Override
public void dismiss() {
super.dismiss();
updateLifecycleStateToDismiss();
}
private void updateLifecycleStateToDismiss(){
registry.setCurrentState( Lifecycle.State.CREATED);
registry.setCurrentState( Lifecycle.State.STARTED);
registry.setCurrentState( Lifecycle.State.DESTROYED);
}
private void updateLifecycleState(){
registry.setCurrentState( Lifecycle.State.CREATED);
registry.setCurrentState( Lifecycle.State.STARTED);
registry.setCurrentState( Lifecycle.State.RESUMED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return registry;
}
}
使用
public class ChangeRzhPopWindow extends EasySizePopupWindow {
public ChangeRzhPopWindow(Context context, View topView) {
super();
setContentView(context, R.layout.change_rzh_pop_window, topView);
//因为背景是我们创建的。所以需要我们实现点击背景关闭的功能
getContentView().findViewById(R.id.close_layout).setOnClickListener(v-> dismiss());
}
}
关于全屏高度问题
如果当前页面的布局是沉浸式的。那么使用PopupWindow的showAsDropDown会出现位置不对的情况。需要加上偏移量。
就可以完美显示了。