PopupWindow的源码解析

更新时间:2018-11-29 13:44:27点击次数:197次
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
}
}

}

}


本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息