問題
在使用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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="#fff"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="44dp"android:layout_margin="12dp"android:background="@drawable/cz_add_rzh_bg"><TextViewandroid: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><Viewandroid: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());}@Overridepublic void showAsDropDown(View anchor) {super.showAsDropDown(anchor);}@Overridepublic void showAsDropDown(View anchor, int xoff, int yoff) {super.showAsDropDown(anchor, xoff, yoff);}@Overridepublic void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {super.showAsDropDown(anchor, xoff, yoff, gravity);updateLifecycleState();}@Overridepublic void showAtLocation(View parent, int gravity, int x, int y) {super.showAtLocation(parent, gravity, x, y);updateLifecycleState();}@Overridepublic 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@Overridepublic 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會出現位置不對的情況。需要加上偏移量。
就可以完美顯示了。