闲来无事把popupwindow的源码看了一遍,能力有限只看懂了一部分。下面我们来看看源码:
<span style="font-size:14px;">private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;</span>
分析:从这里可以看出来PopupWindow(弹出窗口)实际上是一个子窗口,它是一个独立的类(并不继承于Window)。
<span style="font-size:14px;">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); final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle; a.recycle(); setBackgroundDrawable(bg); } </span>
分析:这个一看就很清楚,这是它的构造函数,里面通过context对象获取了windowManager对象和属性typedArray和一些动画style,a.recycle是释放
typedArray。里面还有很多构造函数,主要是对它进行一些参数的初始化,比如设置contentView、宽、高、焦点等。详细请看源码。
接下来主要介绍的是我们经常要用到的显示popupwindow位置的几个方法。因为严格来说这个PopuWindow就是用来在指定位置显示一个View的。主要方法有下面:
public void showAsDropDown(View anchor)
showAsDropDown(View anchor, int xoff, int yoff)
showAtLocation(View parent, int gravity, int x, int y)
下面来看看他们的源码:
public void showAsDropDown(View anchor) { showAsDropDown(anchor, 0, 0); } public void showAsDropDown(View anchor, int xoff, int yoff) { showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY); }<pre name="code" class="java">public void showAtLocation(View parent, int gravity, int x, int y) { showAtLocation(parent.getWindowToken(), gravity, x, y); }
第一个调用的是第二个方法,所以我们直接分析第二个方法:
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { if (isShowing() || mContentView == null) { return; } registerForScrollChanged(anchor, xoff, yoff, gravity); mIsShowing = true; mIsDropdown = true; WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); preparePopup(p); updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; p.windowAnimations = computeAnimationResource(); invokePopup(p); }
<pre name="code" class="java">public void showAtLocation(IBinder token, int gravity, int x, int y) { if (isShowing() || mContentView == null) { return; } unregisterForScrollChanged(); mIsShowing = true; mIsDropdown = false; WindowManager.LayoutParams p = createPopupLayout(token); p.windowAnimations = computeAnimationResource(); preparePopup(p); if (gravity == Gravity.NO_GRAVITY) { gravity = Gravity.TOP | Gravity.START; } p.gravity = gravity; p.x = x; p.y = y; if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; invokePopup(p); }
分析:
先解释下参数:
anchor:就是PopupWindow要显示位置的参照物
xoff: PopupWindow相对于anchor的左下角x轴方向的偏移大小
yoff: PopupWindow相对于anchor的左下角y轴方向的偏移大小
我觉得这两个方法的区别就是showAtLocation能够设置popupwindow显示在参照物view的哪个方位然后设置偏移值,而showAsDropDown则是默认弹出窗显示在参照物view的左下角,然后设置偏移值。代码具体分析:
刚开始isShowing()判断当前PopupWindow是否显示,或者mContentView是否为空,是的话就不用去继续执行下面的代码了。然后unregisterForScrollChanged(),为了滑动改变注册,就是有可能anchor有滑动,或者ContentView过大,有的地方放不开。
private void unregisterForScrollChanged() { WeakReference<View> anchorRef = mAnchor; View anchor = null; if (anchorRef != null) { anchor = anchorRef.get(); } if (anchor != null) { ViewTreeObserver vto = anchor.getViewTreeObserver(); vto.removeOnScrollChangedListener(mOnScrollChangedListener); } mAnchor = null; }就是注册一下滑动的全局的监听,注册之前先注销一下之前的注册,防止ViewTreeObserver失效。 在这里使用了一个弱引用对anchor,防止anchor这个类已经无用了,anchor仍然无法回收内存。 接下来为PopupWindow设置显示参数,调用了createPopupLayout(anchor.getWindowToken())
看下这个方法:
private WindowManager.LayoutParams createPopupLayout(IBinder token) { // generates the layout parameters for the drop down // we want a fixed size view located at the bottom left of the anchor WindowManager.LayoutParams p = new WindowManager.LayoutParams();<span style="color:#ff0000;">//<span style="font-family: Simsun; line-height: 24px;">创建一个WindowManager.LayoutParams的实例</span></span> // 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's bottom // left corner p.gravity = Gravity.START | Gravity.TOP;<span style="color:#ff0000;">//接下来为实例p设置了一系列的参数</span> p.width = mLastWidth = mWidth; p.height = mLastHeight = mHeight; if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } p.flags = computeFlags(p.flags); p.type = mWindowLayoutType; p.token = token; p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p; }
接下来分析方法:invokePopup(p),它才是真正的把PopupWindow显示在特定的位置上。其实就使用Activity中的WindowManager对象对mPopupView进行了添加显示。
<span style="font-size:14px;">private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) { p.packageName = mContext.getPackageName(); } mPopupView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); mWindowManager.addView(mPopupView, p);//<span style="color: rgb(255, 0, 0); font-family: Arial; line-height: 26px;">调用addView完成添加</span> }</span>