關於下拉選擇框,估計你們都有不少選擇,我在之前的文章:項目需求討論-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小結可能寫的不夠全,或者哪裏寫的不對,歡迎你們指出。