PopupWindow还是很常用的,所以为了更好的使用这个控件,今天看看PopupWindow的源码,把其中的原理理一理
1.首先看看构造函数
PopupWindow的构造函数和一些系统控件一样,一层套一层,最后调用的是这个函数
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
接下来看看这个构造函数做了哪些预备工作
首先获取WindowManager,为后续在视图上添加view做准备
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
设置PopupWindow进退场动画(PS:我简化了一些代码,如果看官看到哪里有源码不同,不用在意)
final Transition enterTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupEnterTransition, 0));
final Transition exitTransition = getTransition(a.getResourceId(
R.styleable.PopupWindow_popupExitTransition, 0));
setEnterTransition(enterTransition);
setExitTransition(exitTransition);
并且还设置了背景图片,如果有的话
final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
setBackgroundDrawable(bg);
2.设置ContentView
setContentView作为设置PopupWindow内容视图的函数,他所作的动作真的很少,仅仅保存了contentView这个View类对象,然后判断当前contentView是否为空,之前获取WindowManager是否为空的事情
mContentView = contentView;
然后就是执行setAttachedInDecor给一个变量赋值为true,表示已经在decor里注册了(PS:现在还没有使用WindowManager把PopupWindow添加到DecorView上)
public void setAttachedInDecor(boolean enabled) {
mAttachedInDecor = enabled;
mAttachedInDecorSet = true;
}
3.显示PopupWindow
显示PopupWindow的函数主要有两种
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
public void showAtLocation(View parent, int gravity, int x, int y)
这个两个显示函数很相似,其中的区别我们通过一个一个函数解析来判断
我们先来看看那showAsDropDown
一开始执行了一个attachToAnchor,意思是PopupWindow类似一个锚挂在目标view的下面,这个函数主要讲xoff、yoff(x轴、y轴偏移值)、gravity(比如Gravity.BOTTOM之类,指的是PopupWindow放在目标view哪个方向边缘的位置,)
这个attachToAnchor有点意思,通过弱引用保存目标view和目标view的rootView(PS:防止内存泄漏)、这个rootview是否依附在window、还有保存偏差值、gravity
mAnchor = new WeakReference<>(anchor);
mAnchorRoot = new WeakReference<>(anchorRoot);
mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
mParentRootView = mAnchorRoot;
mAnchorXoff = xoff;
mAnchorYoff = yoff;
mAnchoredGravity = gravity;
然后是创建和初始化LayoutParams
final WindowManager.LayoutParams p =
createPopupLayoutParams(anchor.getApplicationWindowToken());
然后把这个LayoutParams传过去,把PopupWindow真正的样子,也就是view创建出来
private void preparePopup(WindowManager.LayoutParams p)
在这个preparePopup函数里,一开始准备backgroundView,因为一般mBackgroundView是null,所以把之前setContentView设置的contentView作为mBackgroundView
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
然后根据这个backroundView创建DecorView
mDecorView = createDecorView(mBackgroundView);
这个函数的看名字很厉害,其实就是把PopupWindow的根view创建出来,并把contentView添加进去
final PopupDecorView decorView = new PopupDecorView(mContext);
decorView.addView(contentView, MATCH_PARENT, height);
decorView.setClipChildren(false);
decorView.setClipToPadding(false);
这个PopupDecorView继承FrameLayout,其中没有绘画什么,只是复写了dispatchKeyEvent和onTouchEvent之类的事件分发的函数,还有实现进场退场动画的执行函数
回到preparePopup函数执行完后,执行invokePopup(p),这个函数主要将popupView添加到应用DecorView的相应位置,通过之前创建WindowManager完成这个步骤,现在PopupWIndow可以看得到了
mWindowManager.addView(decorView, p);
现在再看看showAtLocation这个函数,说一下他和showAsDropDown的区别,就是关于PopupWindow显示的位置的控制方法不同
showAsDropDown是根据目标view,就像锚一个样挂在目标view周围,然后通过gravity决定挂在哪,和x、y偏差值进行调整
showAtLocation则是以应用DecorView整个应有所占有的屏幕为标准,通过gravity决定在应用哪里出现,和x、y偏差值进行调整
public void showAtLocation(View parent, int gravity, int x, int y)
showAtLocation一开始也是将应用DecorView通过弱引用保存下来,
mParentRootView = new WeakReference<>(parent.getRootView());
接下里就和showAsDropDown一样了,执行三个重要函数
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
4.看看PopupWindow的消失
PopupWindow的消失是通过执行dismiss函数完成的
其中销毁PopupWindow的函数是下面这个函数,这个decorVIew就是PopupVIew,PopupWindow的根视图,用来承载contentView,这个contentHolder也是之前的PopupView
dismissImmediate(decorView, contentHolder, contentView);
然后这个函数注销PopupWindow两个步骤
第一个WindowManager注销PopupView
mWindowManager.removeViewImmediate(decorView);
然后PopupView移除contentView
contentHolder.removeView(contentView);
5.总结
终于结束了,PopupWindow在所有系统控件里算是源码比较少,容易懂的,我们再来总结一下PopupWindow的创建出现、消失有哪些重要操作
(1)创建PopupWindow的时候,首先创建WindowManager,因为WIndowManager拥有控制view的添加和删除、修改的能力
(2)然后是setContentView,保存contentView,这个步骤就做了这个
(3)显示PopupWindow,创建并初始化LayoutParams,作为以后PopupWindow在应用DecorView里哪里显示的凭据。然后创建PopupView,并且将contentView插入其中。最后使用WindowManager将PopupView添加到应用DecorView里。
(4)销毁PopupView,WindowManager把PopupView移除,PopupView再把contentView移除
6.我们学到了什么,可以做到什么
我们知道WindowManager的获取方法,并且可以使用WindowManager给应用DecorView添加view,和删除、修改view
我下面贴一个WindowManager的使用例子供大家参考,这个WindowManager用起来好麻烦,很多参数都要设置,但是你看这些参数能够得出一个View有着很多层面信息,关于view是有token的、还有焦点的初始化设置、关于软键盘的设置、还有背景是否透明之类的
class PopupActivity : AppCompatActivity() {
lateinit var mWindowManager:WindowManager
lateinit var myView:View
var isShow:Boolean=false
lateinit var layoutP:WindowManager.LayoutParams
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_popup)
myView=LayoutInflater.from(this).inflate(R.layout.pop,null,false);
mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
layoutP=WindowManager.LayoutParams()
layoutP.token=btn.applicationWindowToken
layoutP.width=ViewGroup.LayoutParams.WRAP_CONTENT
layoutP.height=ViewGroup.LayoutParams.WRAP_CONTENT
layoutP.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
layoutP.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
layoutP.softInputMode= WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
layoutP.format= PixelFormat.TRANSLUCENT
layoutP.gravity=Gravity.CENTER
btn.setOnClickListener{
if(isShow){
mWindowManager.removeViewImmediate(myView)
isShow=false
}else{
mWindowManager.addView(myView,layoutP)
isShow=true
}
}
}
}