PopWindow位置与自适应
PopWindow 是开发中常用的一种组件,网上对于 PopWindow 的定位方法也众说纷纭,经过看过网上的很多博客,自己也动手尝试了一番,终于对 PopWindow 有了自己的认知,特此记录一下。其实主要是 PopWindow 大小的测量,以及通过相对定位确定 PopWindow 的弹出位置,这样如果遇到了使用场景直接用已有代码即可。
首先是 PopWindow 的大小自适应问题,通过下面这个 Demo 可以非常容易理解,比如下面是 PopWindow 布局文件,在这个布局中间中,PopWindow显示的是 LinearLayout,而不是 RelativeLayout:
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="9999dp"
6 android:layout_height="match_parent">
7
8 <!-- PopWindow显示出的是这个子View -->
9 <LinearLayout
10 android:background="@color/purple_200"
11 android:orientation="vertical"
12 android:padding="10dp"
13 android:layout_width="wrap_content"
14 android:layout_height="wrap_content">
15 <ImageView
16 android:src="@drawable/ic_launcher_foreground"
17 android:layout_width="match_parent"
18 android:layout_height="wrap_content"/>
19 <TextView
20 android:textColor="@color/white"
21 android:text="Hello PopWindow"
22 android:layout_width="match_parent"
23 android:layout_height="wrap_content"/>
24 </LinearLayout>
25</RelativeLayout>
下面是MainActivity的布局文件:
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 tools:context=".MainActivity">
8
9 <Button
10 android:onClick="showPopWindow"
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 android:text="PopWindow"
14 android:textAllCaps="false"
15 app:layout_constraintBottom_toBottomOf="parent"
16 app:layout_constraintLeft_toLeftOf="parent"
17 app:layout_constraintRight_toRightOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20</androidx.constraintlayout.widget.ConstraintLayout>
接下来就是如何放置的问题,如果直接用 popWindow.getHeight() 或者 popWindow.getWidth() 获取的值都是 -2
,其实 PopupWindow 在创建时宽度高度设置为 match_parent 或者 wrap_content 时,通过 getWidth、getHeight 或者 getMeasuredWidth、getMeasuredHeight 均不能获取到真实的高度。
研究过 View 的绘制流程的人应该都知道 ViewRootImpl 的内部函数 performTraversals(),进行measure、layout、draw流程。现在我们只需要提前 measure,测量出 ContentView 的宽和高就行了,而剩下的难度就是如何计算了,这里可以参考 《自定义View —— Measure-测量过程》 。
所以要达到测量PopWindow最终目的其实也很简单,那就是直接对内容布局进行提前 measure。这样的话根据内容宽高很容易就可以对PopWindow进行任意位置摆放。
下面是默认弹窗的位置:
下面主要是使用 showAsDropDown 来对弹窗位置进行控制:
上部居中弹出
1// 上部居中弹出
2public void showTopCenter(PopupWindow popupWindow, View refView){
3 // 获取内容View
4 View contentView = popupWindow.getContentView();
5 // 设置测量模式 UNSPECIFIED
6 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
7 // 获取内容View的宽高->即PopWindow的宽高
8 int pWidth = contentView.getMeasuredWidth();
9 int pHeight = contentView.getMeasuredHeight();
10
11 // 获取参考View的宽高
12 int rWidth = refView.getWidth();
13 int rHeight = refView.getHeight();
14
15 popupWindow.showAsDropDown(refView, -(pWidth/2 - rWidth/2), -pHeight-rHeight);
16}
底部居中弹出
1// 底部居中弹出
2public void showBottomCenter(PopupWindow popupWindow, View refView){
3 // 获取内容View
4 View contentView = popupWindow.getContentView();
5 // 设置测量模式 UNSPECIFIED
6 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
7 // 获取内容View的宽高->即PopWindow的宽高
8 int pWidth = contentView.getMeasuredWidth();
9 int pHeight = contentView.getMeasuredHeight();
10
11 // 获取参考View的宽高
12 int rWidth = refView.getWidth();
13 int rHeight = refView.getHeight();
14
15 popupWindow.showAsDropDown(refView, -(pWidth/2 - rWidth/2), 0);
16}
左边居中弹出
1// 左边居中弹出
2public void showLeftCenter(PopupWindow popupWindow, View refView){
3 // 获取内容View
4 View contentView = popupWindow.getContentView();
5 // 设置测量模式 UNSPECIFIED
6 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
7 // 获取内容View的宽高->即PopWindow的宽高
8 int pWidth = contentView.getMeasuredWidth();
9 int pHeight = contentView.getMeasuredHeight();
10
11 // 获取参考View的宽高
12 int rWidth = refView.getWidth();
13 int rHeight = refView.getHeight();
14
15 popupWindow.showAsDropDown(refView, -pWidth, -(rHeight/2 + pHeight/2));
16}
右边居中弹出
1// 右边居中弹出
2public void showRightCenter(PopupWindow popupWindow, View refView){
3 // 获取内容View
4 View contentView = popupWindow.getContentView();
5 // 设置测量模式 UNSPECIFIED
6 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
7 // 获取内容View的宽高->即PopWindow的宽高
8 int pWidth = contentView.getMeasuredWidth();
9 int pHeight = contentView.getMeasuredHeight();
10
11 // 获取参考View的宽高
12 int rWidth = refView.getWidth();
13 int rHeight = refView.getHeight();
14
15
16 popupWindow.showAsDropDown(refView, rWidth, -(rHeight/2 + pHeight/2));
17}
左上方弹出
1// 左上方弹出
2public void showLeftTop(PopupWindow popupWindow, View refView){
3 // 获取内容View
4 View contentView = popupWindow.getContentView();
5 // 设置测量模式 UNSPECIFIED
6 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
7 // 获取内容View的宽高->即PopWindow的宽高
8 int pWidth = contentView.getMeasuredWidth();
9 int pHeight = contentView.getMeasuredHeight();
10
11 // 获取参考View的宽高
12 int rWidth = refView.getWidth();
13 int rHeight = refView.getHeight();
14
15 popupWindow.showAsDropDown(refView, -pWidth, -pHeight-rHeight);
16}
先写到这里,主要是根据默认位置进行X、Y偏移量计算即可
全部代码:
1import androidx.appcompat.app.AppCompatActivity;
2
3import android.os.Bundle;
4import android.view.LayoutInflater;
5import android.view.View;
6import android.view.ViewGroup;
7import android.widget.PopupWindow;
8
9public class MainActivity extends AppCompatActivity {
10 @Override
11 protected void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14 }
15
16 public void showPopWindow(View button) {
17 View contentView = LayoutInflater.from(this).inflate(R.layout.pop_content, null);
18 PopupWindow popupWindow = new PopupWindow(contentView,
19 ViewGroup.LayoutParams.WRAP_CONTENT,
20 ViewGroup.LayoutParams.WRAP_CONTENT,
21 true);
22 showLeftTop(popupWindow, button);
23 }
24
25 // 上部居中弹出
26 public void showTopCenter(PopupWindow popupWindow, View refView){
27 // 获取内容View
28 View contentView = popupWindow.getContentView();
29 // 设置测量模式 UNSPECIFIED
30 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
31 // 获取内容View的宽高->即PopWindow的宽高
32 int pWidth = contentView.getMeasuredWidth();
33 int pHeight = contentView.getMeasuredHeight();
34
35 // 获取参考View的宽高
36 int rWidth = refView.getWidth();
37 int rHeight = refView.getHeight();
38
39 popupWindow.showAsDropDown(refView, -(pWidth/2 - rWidth/2), -pHeight-rHeight);
40 }
41
42 // 底部居中弹出
43 public void showBottomCenter(PopupWindow popupWindow, View refView){
44 // 获取内容View
45 View contentView = popupWindow.getContentView();
46 // 设置测量模式 UNSPECIFIED
47 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
48 // 获取内容View的宽高->即PopWindow的宽高
49 int pWidth = contentView.getMeasuredWidth();
50 int pHeight = contentView.getMeasuredHeight();
51
52 // 获取参考View的宽高
53 int rWidth = refView.getWidth();
54 int rHeight = refView.getHeight();
55
56 popupWindow.showAsDropDown(refView, -(pWidth/2 - rWidth/2), 0);
57 }
58
59
60 // 左边居中弹出
61 public void showLeftCenter(PopupWindow popupWindow, View refView){
62 // 获取内容View
63 View contentView = popupWindow.getContentView();
64 // 设置测量模式 UNSPECIFIED
65 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
66 // 获取内容View的宽高->即PopWindow的宽高
67 int pWidth = contentView.getMeasuredWidth();
68 int pHeight = contentView.getMeasuredHeight();
69
70 // 获取参考View的宽高
71 int rWidth = refView.getWidth();
72 int rHeight = refView.getHeight();
73
74 popupWindow.showAsDropDown(refView, -pWidth, -(rHeight/2 + pHeight/2));
75 }
76
77 // 右边居中弹出
78 public void showRightCenter(PopupWindow popupWindow, View refView){
79 // 获取内容View
80 View contentView = popupWindow.getContentView();
81 // 设置测量模式 UNSPECIFIED
82 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
83 // 获取内容View的宽高->即PopWindow的宽高
84 int pWidth = contentView.getMeasuredWidth();
85 int pHeight = contentView.getMeasuredHeight();
86
87 // 获取参考View的宽高
88 int rWidth = refView.getWidth();
89 int rHeight = refView.getHeight();
90
91 popupWindow.showAsDropDown(refView, rWidth, -(rHeight/2 + pHeight/2));
92 }
93
94
95 // 左上方弹出
96 public void showLeftTop(PopupWindow popupWindow, View refView){
97 // 获取内容View
98 View contentView = popupWindow.getContentView();
99 // 设置测量模式 UNSPECIFIED
100 contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
101 // 获取内容View的宽高->即PopWindow的宽高
102 int pWidth = contentView.getMeasuredWidth();
103 int pHeight = contentView.getMeasuredHeight();
104
105 // 获取参考View的宽高
106 int rWidth = refView.getWidth();
107 int rHeight = refView.getHeight();
108
109 popupWindow.showAsDropDown(refView, -pWidth, -pHeight-rHeight);
110 }
111}