弹出框 PopupWindow
是android
中的一个控件,可以显示给用户看到。不过,它并不是View
的子类,是一个独立的类,没有父类,或者说是继承自Object
。
这个类提供了很多构造方法,但是大体是两类,一类是需要传入view
的,一类是需要传入Context
的。
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);
}
传入View
的,最终是调用了上面的方法。这个方法会创建一个WindowManager
对象。这个很重要,因为PopupWindow
里面控件的展示与隐藏,就是调用wm.addView()
,wm.removeView()
来完成的。
第二种是需要传入一个Context
的。
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
// Preserve default behavior from Gingerbread. If the animation is
// undefined or explicitly specifies the Gingerbread animation style,
// use a sentinel value.
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
if (animStyle == R.style.Animation_PopupWindow) {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
} else {
mAnimationStyle = animStyle;
}
} else {
mAnimationStyle = ANIMATION_STYLE_DEFAULT;
}
final Transition enterTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupEnterTransition, 0));
final Transition exitTransition;
if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
exitTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupExitTransition, 0));
} else {
exitTransition = enterTransition == null ? null : enterTransition.clone();
}
a.recycle();
setEnterTransition(enterTransition);
setExitTransition(exitTransition);
setBackgroundDrawable(bg);
}
里面代码很多,但是不复杂。也是创建一个WindowManager
对象。
然后看一下显示的方法。显示也是分两种。
public void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parent.getRootView());
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}
看到这里有两个关键方法:preparePopup()
, invokePopup(p);
主要看后面一个。
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p); // todo: 显示出来
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
另一种显示,最终也是调用到这里了,就不说了。最后看一下隐藏的方法。
private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
// If this method gets called and the decor view doesn't have a parent,
// then it was either never added or was already removed. That should
// never happen, but it's worth checking to avoid potential crashes.
if (decorView.getParent() != null) {
mWindowManager.removeViewImmediate(decorView); // todo: 移除显示
}
if (contentHolder != null) {
contentHolder.removeView(contentView);
}
// This needs to stay until after all transitions have ended since we
// need the reference to cancel transitions in preparePopup().
mDecorView = null;
mBackgroundView = null;
mIsTransitioningToDismiss = false;
}
当然,PopupWindow
里面还有很多计算位置,判断显示背景之类的方法。或许还有其他的。但是,创建,显示,隐藏的逻辑就是上面分析的这样。
通过分析可以得出一个粗略的结论,就是PopupWindow
是对WindowManager
的一次细化封装。
以上~