关于下拉选择框,估计你们都有不少选择,我在之前的文章:项目需求讨论-HyBrid模式需求改造 上写过下拉框选择这一块,正好用的Spinner。android
此次正好又有一个下拉框的需求,因此此次我使用了PopupWindow来实现的。而后想到其实PopupWindow不少地方都会用到,可是一直没有好好的总结过,因此就想到了写本文,并且本文也十分的基础和简单,你们也很好理解。数组
主要分为三部分:bash
咱们知道上来直接给一大串的源码,不多有人会继续看下去,因此咱们就本身先写个下拉选择框demo来进行演示。app
因此咱们能够先来看下咱们须要的下拉框样式:(为了随便举个例子,因此设计的比较丑):ide
咱们能够一步步来看如何实现:函数
既然要跳出下面的弹框,并且本文说过要使用PopupWindow,因此就是实现一个PopupWindow便可,十分简单。工具
既然实例化PopupWindow对象,因此咱们看下它的构造函数:源码分析
public PopupWindow() {
this(null, 0, 0);
}
public PopupWindow(View contentView) {
this(contentView, 0, 0);
}
public PopupWindow(int width, int height) {
this(null, width, height);
}
public PopupWindow(View contentView, int width, int height) {
this(contentView, width, height, false);
}
/**
@param contentView the popup content
@param width the popup's width @param height the popup's height
@param focusable true if the popup can be focused, false otherwise
*/
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
复制代码
咱们能够看到无论你用的哪一个构造函数,最终必定是调用了最后一个构造函数:PopupWindow(View contentView, int width, int height, boolean focusable)
布局
也就是说咱们要告诉PopupWindow这些内容:动画
假设咱们用的第四个构造函数
View contentView = LayoutInflater.from(MainActivity.this).inflate(R.layout.popuplayout, null);
PopupWindow popupWindow = new PopupWindow(contentView,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,true);
复制代码
固然咱们也可使用第一个构造函数生成对象,而后经过相应的SetXXXX方法,设置各类参数。
咱们来看下一些经常使用的Set方法:
设置contentView, 宽和高,获取焦点能力:
popupWindow.setContentView(contentView);
popupWindow.setHeight(height);
popupWindow.setWidth(width);
popupWindow.setFocusable(true);
复制代码
点击窗体外消失:
// 须要设置一下PopupWindow背景,点击外边消失才起做用
popupWindow.setBackgroundDrawable(new BitmapDrawable(getResources(),(Bitmap) null));
// 点击窗外可取消
popupWindow.setTouchable(true);
popupWindow.setOutsideTouchable(true);
复制代码
关于窗体会被软件盘遮挡:
// 设置pop被键盘顶上去,而不是遮挡
popupWindow.setSoftInputMode(PopupWindow.INPUT_METHOD_NEEDED);
popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
复制代码
popupwindow添加各类动画效果(平移,缩放,透明等):
popupWindow.setAnimationStyle(R.style.popwindow_anim_style);
复制代码
动画的style:
<style name="AnimDown" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/push_scale_in</item>
<item name="android:windowExitAnimation">@anim/push_scale_out</item>
</style>
复制代码
具体的动画:
<!-- 显示动画-->
<?xml version="1.0" encoding="utf-8"?><!-- 左上角扩大-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true">
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromXScale="1.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
复制代码
<!-- 隐藏动画-->
<?xml version="1.0" encoding="utf-8"?><!-- 左上角扩大-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true">
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXScale="1.0"
android:toYScale="0.001" />
</set>
复制代码
主要是使用showXXXX方法来实现,而这个方法也有好几个:
咱们先来看showAsDropDown
和showAtLocation
的区别: 不少人估计用的更多的是showAsDropDown,它们的最大区别简单来讲是showAsDropDown是相对于某个控件,而后PopupWindow显示在这个控件的下方;而showAtLocation是相对于屏幕,能够经过设置Gravity来指定PopupWindow显示在屏幕的那个位置。
好比咱们如今先看showAsDropDown
:
//PopupWindow会显示咱们传入的这个View的下方,平切是左边对齐
//(也就是view控件的左下角与popupWindow的左上角对齐)
showAsDropDown(View)
复制代码
//PopupWindow仍是在这个View的下方,
//可是额外能够设置x,y的偏移值,x,y表示坐标偏移量
showAsDropDown(View,int,int);
复制代码
好比咱们代码写为:showAsDropDown(View,50,50);X轴和Y轴都偏移了50。
//PopupWindow能够额外设定Gravity,默认就是Gravity.Left。
//同时设置为Top和Bottom没啥效果,由于是在这个View的下方。
showAsDropDown(View,int,int,int);
复制代码
好比咱们代码写为:popupWindow.showAsDropDown(v,0,0,Gravity.RIGHT);变成了View的右下角与PopupWindow的左上角对齐了。
咱们再来看showAtLocation
: 由于这个方法是PopupWindow的显示相对于屏幕,因此传入的View也是只要这个屏幕的就能够,由于这个View的传入也只是为了拿到Window Token。
//这个方法最后仍是等于调用了另一个showAtLocation方法,
//传入view只是为了拿到token
//x,y一样是x和y轴的偏移值
public void showAtLocation(View parent, int gravity, int x, int y) {
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
public void showAtLocation(IBinder token, int gravity, int x, int y){
.......
}
复制代码
好比咱们写入的代码是:popupWindow.showAtLocation(view, Gravity.RIGHT | Gravity.BOTTOM, 0, 0);
若是咱们设置为:popupWindow.showAtLocation(view, Gravity.TOP, 0, 0);
咱们发现PopupWindow并无在statusbar的上面。若是咱们想要覆盖statusbar呢,能够再加一句:popupWindow.setClippingEnabled(false);
因此基本使用估计你们都会了。咱们来总结下代码:
LayoutInflater mLayoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
//自定义布局
ViewGroup view = (ViewGroup) mLayoutInflater.inflate(R.layout.window, null, true);
PopupWindow popupWindow = new PopupWindow(view, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT, true);
//是否须要点击PopupWindow外部其余界面时候消失
mPopWindow.setBackgroundDrawable(new BitmapDrawable());
mPopWindow.setOutsideTouchable(true);
//设置touchable和focusable
mPopWindow.setFocusable(true);
mPopWindow.setTouchable(true);
/**
而后好比在某个按钮的点击事件中显示PopupWindow
切记不能直接在好比onCreate中直接调用显示popupWindow,
会直接抛出异常,缘由后面源码解析会提到
*/
btn.setOnclickListener(v -> {
if (popupWindow != null) {
popupWindow.showAsDropDown(v);
}
})
复制代码
我在之前写过Dialog的封装文章:
项目需求讨论-Android 自定义Dialog实现步骤及封装
咱们此次来对PopupWindow来进行封装,咱们仍是像上面的文章那样,使用Builder模式。
咱们先来看咱们要注意哪些因素要考虑:
因此初步咱们能够写成这样:
public class CustomPopupWindow extends PopupWindow {
private CustomPopupWindow(Builder builder) {
super(builder.context);
builder.view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
setContentView(builder.view);
setHeight(builder.height == 0?ViewGroup.LayoutParams.WRAP_CONTENT:builder.height);
setWidth(builder.width == 0?ViewGroup.LayoutParams.WRAP_CONTENT:builder.width);
if (builder.cancelTouchout) {
setBackgroundDrawable(new ColorDrawable(0x00000000));//设置透明背景
setOutsideTouchable(builder.cancelTouchout);//设置outside可点击
}
setFocusable(builder.isFocusable);
setTouchable(builder.isTouchable);
if(builder.animStyle != 0){
setAnimationStyle(builder.animStyle);
}
}
public static final class Builder {
private Context context;
private int height, width;
private boolean cancelTouchout;
private boolean isFocusable = true;
private boolean isTouchable = true;
private View view;
private int animStyle;
public Builder(Context context) {
this.context = context;
}
public Builder view(int resView) {
view = LayoutInflater.from(context).inflate(resView, null);
return this;
}
public Builder view(View resVew){
view = resVew;
return this;
}
public Builder heightpx(int val) {
height = val;
return this;
}
public Builder widthpx(int val) {
width = val;
return this;
}
public Builder heightdp(int val) {
height = dip2px(context, val);
return this;
}
public Builder widthdp(int val) {
width = dip2px(context, val);
return this;
}
public Builder heightDimenRes(int dimenRes) {
height = context.getResources().getDimensionPixelOffset(dimenRes);
return this;
}
public Builder widthDimenRes(int dimenRes) {
width = context.getResources().getDimensionPixelOffset(dimenRes);
return this;
}
public Builder cancelTouchout(boolean val) {
cancelTouchout = val;
return this;
}
public Builder isFocusable(boolean val) {
isFocusable = val;
return this;
}
public Builder isTouchable(boolean val) {
isTouchable = val;
return this;
}
public Builder animStyle(int val){
animStyle = val;
return this;
}
public Builder addViewOnclick(int viewRes, View.OnClickListener listener) {
view.findViewById(viewRes).setOnClickListener(listener);
return this;
}
public CustomPopupWindow build() {
return new CustomPopupWindow(this);
}
}
@Override
public int getWidth() {
return getContentView().getMeasuredWidth();
}
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}
复制代码
因此只要知道咱们要设定哪些属性,就很容易封装。
而后使用就能够:
customPopupWindow = new CustomPopupWindow.Builder(this)
.cancelTouchout(true)
.view(popupWindowView)
.isFocusable(true)
.animStyle(R.style.AnimDown)
.build();
复制代码
这里我要额外提上面封装类代码中的二个知识点:
咱们能够看到在咱们的工具类中,有一段代码:
builder.view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
,
就是把咱们传进去的contentView提早绘制,这样咱们就能够调用popupwindow.getContentView().getMeasuredWidth()
方法来获取这个contentView的宽高了(ps:咱们通常设置的popupwindow的宽高确定跟咱们传进去的contentview一致)。
可能有些人就会问了,咱们为啥须要提早知道popupwindow的宽高呢,好比下面这个需求:
好比上面的启动PopupWindow的按钮,比下面的选项宽,咱们确定但愿我们的PopupWindow是显示在正中间,因此咱们在调用:
showAsDropDown(View anchor, int xoff, int yoff);
复制代码
时候传入的X值的偏移量就要为上面的按钮宽度
减去下面PopupWindow的宽度
后的一半。可是日常状况下,咱们单纯经过PopupWindow.getWidth()
或者contentView.getWidth()
方法,在第一次点击出现的时候,获取到的值前者为-2,后者为0,而后再次点击的时候就是正确值了。由于第一次点击前,PopupWindow还没出如今屏幕过,因此也没有被绘制出来过,宽度固然也获取不到准确值了。出现过一次后,第二次点击就能正确获取了。因此第一次PopupWindow就出如今错误位置,后面就对了。
因此咱们从新重载了PopupWindow
的getWidth
方法:
@Override
public int getWidth() {
return getContentView().getMeasuredWidth();
}
复制代码
咱们通常对上面的按钮设置成这样:
btn.setOnclickListener(v -> {
if (popupWindow != null) {
popupWindow.showAsDropDown(v);
}
})
复制代码
这样点击按钮后就能够出现咱们的PopupWindow,可是你再次点击这个按钮,PopupWindow会先消失,而后再次出现,就像下面这样:
可是咱们但愿的是点击按钮后,若是PopupWindow在的话就消失。
固然你能够在点击事件里面用:PopupWindow.isShowing();
判断,而后让PopupWindow.dismiss();
,可是别人用了咱们的工具类,总不能还要告诉它要在触发按钮点击事件里面要额外判断吧,因此咱们只须要在咱们工具类中默认设置PopupWindow的touchable
和focusable
为true
,这样,咱们的点击事件啥都不用改,就能够点击一下出现,再点击消失。
很惭愧,很早之前就会用PopupWindow,可是源码一直没有去看过。
在讲解PopupWindow源码前咱们先来看下其余的知识。
咱们应该都作过或者看见过添加悬浮窗等功能,或者在某些文章看见过Window和WindowManager的介绍,好比在《Android艺术开发之旅》里面,也有相关的一章专门讲这个,你们能够看下:
Android开发艺术探索——第八章:理解Window和WindowManager
假设咱们如今要在应用程序的某处加个按钮,应该怎么样呢:
Button btn = new Button(this);
btn.setText("我是窗口");
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams layout = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT
, WindowManager.LayoutParams.WRAP_CONTENT, 0,0,
PixelFormat.TRANSLUCENT);
layout.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layout.gravity = Gravity.CENTER;
layout.type = WindowManager.LayoutParams.TYPE_APPLICATION;
layout.x = 300;
layout.y = 100;
wm.addView(btn, layout);
复制代码
只须要经过WindowManager的addView方法,把这个按钮加进来便可,我估计有百分之八九十的安卓开发都大概见过或者知道这种经过WindowManager添加的方式。
咱们能够看出有这么几步:
PS:这里额外提下layout.type = WindowManager.LayoutParams.TYPE_APPLICATION;这个属性,好比咱们当前只是在咱们的app里面加一个按钮,因此也不须要作其余额外处理;若是咱们是想全局添加按钮,也就是咱们的app最小化到了后台,在手机桌面仍是能看到有个按钮悬浮(相似一些手机清理助手等悬浮小球),须要切换这里的type属性,同时还要声明相应的权限,否则app就会报错,说permission denied for this window type。相应的type介绍你们能够参考:WindowManager.LayoutParams的type属性
没错,我们的PopupWindow也是相似的。
咱们从构造函数开始看起来:
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
复制代码
咱们能够看到,果真获取了WindowManager
对象,而后给PopupWindow的内部的contentView、width、height、focusable
赋值。
咱们看最后显示的方法源码:
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
attachToAnchor(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
//'咱们能够看到这里果真生成了相应的WindowManager.LayoutParams'
final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
//'把这个LayoutParams传过去,把PopupWindow真正的样子,也就是view建立出来'
preparePopup(p);
//'findDropDownPosition方法肯定好PopupWindow要显示的位置'
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
p.width, p.height, gravity);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
//'最终调用windowmanager.addview方法呈现popupwindow'
invokePopup(p);
}
复制代码
咱们能够看到建立WindowManager.LayoutParams
是经过代码 final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
咱们具体来看下这个方法
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location by
// setting the x and y offsets to match the anchor bottom-left
// corner.
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
复制代码
咱们再看preparePopup(p);
方法:
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by calling setContentView() before attempting to show the popup.");
}
// The old decor view may be transitioning out. Make sure it finishes
// and cleans up before we try to create another one.
if (mDecorView != null) {
mDecorView.cancelTransitions();
}
// When a background is available, we embed the content view within
// another view that owns the background drawable.
/**
'准备backgroundView,由于通常mBackgroundView是null, 因此把以前setContentView设置的contentView做为mBackgroundView, 否则就生成一个PopupBackgroundView(继承FrameLayout), 把contentView加进去,而后再对这个PopupBackgroundView设置背景'
*/
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
/**
'生成相应的PopupWindow的根View。 实际也就是实例一个PopupDecorView(继承FrameLayout),而后把contentView add进来 (ps:是否是想起Activity的根view:DecorView,也是叫这个名字,也是把Activity的contentView加进来)'
*/
mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we will need to manually specify
// the surface insets.
p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
}
复制代码
主要是经过源码中的下面这个方法:
findDropDownPosition(anchor, p, xoff, yoff,p.width, p.height, gravity);
复制代码
由于咱们可能让PopupWindow出如今咱们点击按钮的下面,因此咱们会传入按钮的View,咱们知道咱们让PopupWindow出如今按钮下方,确定须要设置WindowManager.LayoutParams的x,y值,才能让它出如今指定位置,因此咱们确定要根据按钮的View,获取它的x,y值,而后额外加上咱们后来传进来的x,y轴的偏移值,而后最后显示。
咱们具体查看源码的内容:
private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
int xOffset, int yOffset, int width, int height, int gravity) {
final int anchorHeight = anchor.getHeight();
final int anchorWidth = anchor.getWidth();
if (mOverlapAnchor) {
yOffset -= anchorHeight;
}
// Initially, align to the bottom-left corner of the anchor plus offsets.
final int[] drawingLocation = mTmpDrawingLocation;
/**
'咱们能够看到调用了getLocationInWindow方法, 来获取咱们参考的View的当前窗口内的绝对坐标, 获得的值为数组: location[0] -----> x坐标 location[1] -----> y坐标'
*/
anchor.getLocationInWindow(drawingLocation);
//'咱们的PopupWindow的x为当前的参考View的x值加上咱们额外传入的偏移值'
outParams.x = drawingLocation[0] + xOffset;
//'咱们的PopupWindow的y为当前的参考View的y值加上咱们参考view的高度及额外传入的偏移值'
outParams.y = drawingLocation[1] + anchorHeight + yOffset;
final Rect displayFrame = new Rect();
anchor.getWindowVisibleDisplayFrame(displayFrame);
if (width == MATCH_PARENT) {
width = displayFrame.right - displayFrame.left;
}
if (height == MATCH_PARENT) {
height = displayFrame.bottom - displayFrame.top;
}
// Let the window manager know to align the top to y.
outParams.gravity = computeGravity();
outParams.width = width;
outParams.height = height;
// If we need to adjust for gravity RIGHT, align to the bottom-right
// corner of the anchor (still accounting for offsets).
final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
& Gravity.HORIZONTAL_GRAVITY_MASK;
/**
'若是是Gravity.RIGHT,咱们的x值还须要再作偏移, 至关于减去(咱们的PopupWindow宽度减去参考View的宽度)。'
*/
if (hgrav == Gravity.RIGHT) {
outParams.x -= width - anchorWidth;
}
final int[] screenLocation = mTmpScreenLocation;
anchor.getLocationOnScreen(screenLocation);
// First, attempt to fit the popup vertically without resizing.
final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
displayFrame.bottom, false);
// Next, attempt to fit the popup horizontally without resizing.
final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width,
anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left,
displayFrame.right, false);
// If the popup still doesn not fit, attempt to scroll the parent.
if (!fitsVertical || !fitsHorizontal) {
final int scrollX = anchor.getScrollX();
final int scrollY = anchor.getScrollY();
final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
scrollY + height + anchorHeight + yOffset);
if (mAllowScrollingAnchorParent && anchor.requestRectangleOnScreen(r, true)) {
// Reset for the new anchor position.
anchor.getLocationInWindow(drawingLocation);
outParams.x = drawingLocation[0] + xOffset;
outParams.y = drawingLocation[1] + anchorHeight + yOffset;
// Preserve the gravity adjustment.
if (hgrav == Gravity.RIGHT) {
outParams.x -= width - anchorWidth;
}
}
// Try to fit the popup again and allowing resizing.
tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1],
screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen);
tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0],
screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen);
}
// Return whether the popup top edge is above the anchor top edge.
return outParams.y < drawingLocation[1];
}
复制代码
经过最后的invokePopup(p);
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
//'最后经过windowmanager的addview方法把decorView加进来'
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
复制代码
补充1:固然咱们日常也知道用WindowManager.removeView或者removeViewImmediate方法移除View,而咱们的PopupWindow.dismiss()方法也是同样,使用了
mWindowManager.removeViewImmediate(decorView);
移除,这步我就很少说了。你们能够本身看下。
补充2:看懂了showAsDropDown的源码,showAsLocation的就更简单了,直接让LayoutParams的x和y值等于你传入的x,y值,其余代码都是相似的。
补充3:咱们前面提过在onCreate方法里面直接显示ShowAsDropDown等显示方法会报错:android.view.WindowManager$BadTokenException,由于这时候Activity的相关View都没初始化好,也就拿到的view.token为null了。
PopupWindow小结可能写的不够全,或者哪里写的不对,欢迎你们指出。