Android源码分析

Android基础 (11) PopupWindow详解

2019-02-19  本文已影响90人  perry_Fan

(1)PopupWindow的使用
(2)自定义一个PopupWindow
(3)PopupWindow的源码分析
(4)AlertDialog,popupWindow,Activity区别
(5)Activity-Window-View三者的差别

一.使用方式

Android的对话框有两种:PopupWindow和AlertDialog。它们的不同点在于:

PopupWindow的位置按照有无偏移分,可以分为偏移和无偏移两种;按照参照物的不同,可以分为相对于某个控件(Anchor锚)和相对于父控件。具体如下:
showAsDropDown(View anchor):相对某个控件的位置(正左下方),无偏移
showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置,有偏移
showAtLocation(View parent, int gravity, int x, int y):相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移。

使用详述:https://blog.csdn.net/xiaanming/article/details/9121383

二. 源码分析

1.最简单的创建方法
1.1 PopupWindow构造方法
public PopupWindow (Context context)
public PopupWindow(View contentView)
public PopupWindow(int width, int height)
public PopupWindow(View contentView, int width, int height)
public PopupWindow(View contentView, int width, int height, boolean focusable)
1.2 显示PopupWindow
showAsDropDown(View anchor):相对某个控件的位置(正左下方),无偏移
showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置,有偏移
showAtLocation(View parent, int gravity, int x, int y):相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移
1.3 最简单的创建
//创建对象
PopupWindow popupWindow = new PopupWindow(this);
View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
//设置view布局
popupWindow.setContentView(inflate);
popupWindow.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
//设置动画的方法
popupWindow.setAnimationStyle(R.style.BottomDialog);
//设置PopUpWindow的焦点,设置为true之后,PopupWindow内容区域,才可以响应点击事件
popupWindow.setTouchable(true);
//设置背景透明
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
//点击空白处的时候让PopupWindow消失
popupWindow.setOutsideTouchable(true);
// true时,点击返回键先消失 PopupWindow
// 但是设置为true时setOutsideTouchable,setTouchable方法就失效了(点击外部不消失,内容区域也不响应事件)
// false时PopupWindow不处理返回键,默认是false
popupWindow.setFocusable(false);
//设置dismiss事件
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
    @Override
    public void onDismiss() {

    }
});
boolean showing = popupWindow.isShowing();
if (!showing){
    //show,并且可以设置位置
    popupWindow.showAsDropDown(mTv1);
}
1.4 注意问题:宽和高属性
PopupWindow popupWindow = new PopupWindow(this);
View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
popupWindow.setContentView(inflate);
popupWindow.setAnimationStyle(R.style.BottomDialog);
popupWindow.showAsDropDown(mTv1);

这里的WRAP_CONTENT可以换成fill_parent 也可以是具体的数值,它是指PopupWindow的大小,也就是contentView的大小,注意popupWindow根据这个大小显示你的View,如果你的View本身是从xml得到的,那么xml的第一层view的大小属性将被忽略。相当于popupWindow的width和height属性直接和第一层View相对应。

2.源码分析
2.1 setContentView(View contentView)源码分析
public void setContentView(View contentView) {
    //判断是否show,如果已经show,则返回
    if (isShowing()) {
        return;
    }
    //赋值
    mContentView = contentView;

    if (mContext == null && mContentView != null) {
        mContext = mContentView.getContext();
    }

    if (mWindowManager == null && mContentView != null) {
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    }

    //在这里根据SDK版本而不是在构造函数中设置附加InDecor的默认设置,因为构造函数中可能没有上下文对象。
   // 我们只想在这里设置默认,如果应用程序尚未设置附加InDecor。
    if (mContext != null && !mAttachedInDecorSet) {
        setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
                >= Build.VERSION_CODES.LOLLIPOP_MR1);
    }
}
public void setAttachedInDecor(boolean enabled) {
    mAttachedInDecor = enabled;
    mAttachedInDecorSet = true;
}
2.2 showAsDropDown()源码
public void showAsDropDown(View anchor) {
    showAsDropDown(anchor, 0, 0);
}

//主要看这个方法
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
    if (isShowing() || mContentView == null) {
        return;
    }

    TransitionManager.endTransitions(mDecorView);

    attachToAnchor(anchor, xoff, yoff, gravity);

    mIsShowing = true;
    mIsDropdown = true;

    //通过createPopupLayoutParams方法创建和初始化LayoutParams
    final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
    preparePopup(p);

    final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
            p.width, p.height, gravity);
    updateAboveAnchor(aboveAnchor);
    p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

    invokePopup(p);
}

执行了一个attachToAnchor,意思是PopupWindow类似一个锚挂在目标view的下面,这个函数主要讲xoff、yoff(x轴、y轴偏移值)、gravity(比如Gravity.BOTTOM之类,指的是PopupWindow放在目标view哪个方向边缘的位置)这个attachToAnchor有点意思,通过弱引用保存目标view和目标view的rootView(我们都知道:通过弱引用和软引用可以防止内存泄漏)、这个rootview是否依附在window、还有保存偏差值、gravity

private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
    detachFromAnchor();

    final ViewTreeObserver vto = anchor.getViewTreeObserver();
    if (vto != null) {
        vto.addOnScrollChangedListener(mOnScrollChangedListener);
    }

    final View anchorRoot = anchor.getRootView();
    anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);

    mAnchor = new WeakReference<>(anchor);
    mAnchorRoot = new WeakReference<>(anchorRoot);
    mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();

    mAnchorXoff = xoff;
    mAnchorYoff = yoff;
    mAnchoredGravity = gravity;
}
2.3 dismiss()源码分析
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);
    }

    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;
}
2.4 PopupDecorView源码分析
private class PopupDecorView extends FrameLayout {
   private TransitionListenerAdapter mPendingExitListener;

   public PopupDecorView(Context context) {
       super(context);
   }
   @Override
   public boolean onTouchEvent(MotionEvent event) {
       final int x = (int) event.getX();
       final int y = (int) event.getY();

       if ((event.getAction() == MotionEvent.ACTION_DOWN)
               && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
           dismiss();
           return true;
       } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
           dismiss();
           return true;
       } else {
           return super.onTouchEvent(event);
       }
   }
}
3.经典总结
3.1 PopupWindow和Dialog有什么区别?
3.2 创建和销毁的大概流程
3.3 为何弹窗点击一下就dismiss呢?

PopupWindow通过为传入的View添加一层包裹的布局,并重写该布局的点击事件,实现点击PopupWindow之外的区域PopupWindow消失的效果

封装库可前往:#### https://github.com/yangchong211/YCDialog

三者关系
上一篇 下一篇

猜你喜欢

热点阅读