PopupWindow的源码解析

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
            }
        }

    }

}

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值