读源码是为了了解并学习它的实现机制,并更好的运用它,如果在读源码之前已经知道它的怎么运用,这将会更容易理解源码。所以在这读源码开头我推荐阅读一下一位大神写的相关博文,浅显易懂,条理清晰:
PopUpWindow使用详解(一)——基本使用
PopUpWindow使用详解(二)——进阶及答疑
PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是一个浮动的容器,悬浮在当前activity之上的.
PopupWindow可以说是一种view,但它不同于一般的view,它不继承自view类或其子类,而是直接继承自Object ,是基于WindowManager出生的。
PopupWindow类中有一个接口,两个内部类,从内部类从这里切入,方便理解与PopupWindow密切相关的实例,从而更容易理顺类的思路。

一,内部的接口:OnDismissListener

当弹出框被dismissed(解雇,驳回,消失)调用这个接口.其内结构非常简单:

public interface OnDismissListener {public void onDismiss();}

看到这个接口,相信都很有熟悉感,因为Dialog中也有一个一样的接口。Dialog通过setOnDismissListener(OnDismissListener onDismissListener)在点击对话框按钮消失后执行设置的行为(如dismissDialog(int id) ),PopupWindow的这个接口和对话框的功能相同,在PopupWindow同样可以找到对接口OnDismissListener进行包装的和对话框的类一样的setOnDismissListener(OnDismissListener onDismissListener)方法:

 public void setOnDismissListener(OnDismissListener onDismissListener) {mOnDismissListener = onDismissListener;}

用法也一样。

二,弹出框的根view:PopupDecorView

如果理解什么是DecorView,看到这个类名就会知道它的作用。DecorView是窗口界面所有视图的根,关于它推荐阅读Android中将布局文件/View添加至窗口过程分析 —- 从setContentView()谈起
PopupDecorView从字面意思猜测可能就是弹出框的根视图了。就是下图弹出框带来的阴影。

图片来自:PopUpWindow使用详解(一)——基本使用

PopupDecorView继承自FrameLayout,其内部七个方法可以分为两部分:事件分发和进出动画。

三,弹出框的背景view:PopupBackgroundView

这个内部类定义了弹出框的背景视图:

private class PopupBackgroundView extends FrameLayout {public PopupBackgroundView(Context context) {super(context);}@Overrideprotected int[] onCreateDrawableState(int extraSpace) {if (mAboveAnchor) {final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);return drawableState;} else {return super.onCreateDrawableState(extraSpace);}}}

之所以贴出这段代码,是推荐学习这种Drawable式的自定义方式,可以参考学习Android Drawable 那些不为人知的高效用法

四,PopupWindow

先定义了三个与输入法相关的常量:INPUT_METHOD_FROM_FOCUSABLE,INPUT_METHOD_NEEDED,INPUT_METHOD_NOT_NEEDED。这些常量表示弹出框与输入法弹出框的关系。接着声明一些成员变量并且或初始化值。
值得注意的是PopupWindow有九个构造器,这些构造器可以分为两组,代表分别为:

public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//1,获取contextmContext = context;
//2,获取WindowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//3,加载属性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);// Preserve default behavior from Gingerbread. If the animation is// undefined or explicitly specifies the Gingerbread animation style,// use a sentinel value.if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);if (animStyle == R.style.Animation_PopupWindow) {mAnimationStyle = ANIMATION_STYLE_DEFAULT;} else {mAnimationStyle = animStyle;}} else {mAnimationStyle = ANIMATION_STYLE_DEFAULT;}final Transition enterTransition = getTransition(a.getResourceId(R.styleable.PopupWindow_popupEnterTransition, 0));final Transition exitTransition;if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {exitTransition = getTransition(a.getResourceId(R.styleable.PopupWindow_popupExitTransition, 0));} else {exitTransition = enterTransition == null ? null : enterTransition.clone();}
//4,a.recycle();
//5,读取资源进行相关设置setEnterTransition(enterTransition);//设置动画setExitTransition(exitTransition);setBackgroundDrawable(bg);//设置背景}
 public PopupWindow(View contentView, int width, int height, boolean focusable) {if (contentView != null) {
//1,获取ContextmContext = contentView.getContext();
//2,获取WindowManagermWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);}
//3,构造器参数setContentView(contentView);setWidth(width);setHeight(height);setFocusable(focusable);}

然后其他的构造器都是这两个构造器的参数依次设为了默认。这两组构造器侧重点不同,在使用的时候可以根据情景需要和使用习惯进行选择。上面的第一组构造器方法在我们平时的自定义view中比较典型,所以如果记住会在自定义view中有所帮助。并且在其中值得注意的是在TypedArray后的recycle()调用,在TypedArray后调用recycle主要是为了缓存Style中属性,重复使用。当recycle被调用后,这就说明这个对象从现在可以被重用了。TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。recycle()源码为:

 /*** Give back a previously retrieved StyledAttributes, for later re-use.*/public void recycle() {synchronized (mResources.mTmpValue) {TypedArray cached = mResources.mCachedStyledAttributes;if (cached == null || cached.mData.length < mData.length) {mXml = null;mResources.mCachedStyledAttributes = this;}}}

接下来是一些属性方法的set和get方法,然后这个类中的比较重要的逻辑方法:

public void showAtLocation(IBinder token, int gravity, int x, int y) {
//1if (isShowing() || mContentView == null) {return;}
//2TransitionManager.endTransitions(mDecorView);
//3unregisterForScrollChanged();
//4mIsShowing = true;mIsDropdown = false;
//5final WindowManager.LayoutParams p = createPopupLayoutParams(token);preparePopup(p);
//6// Only override the default if some gravity was specified.if (gravity != Gravity.NO_GRAVITY) {p.gravity = gravity;}p.x = x;p.y = y;
//7invokePopup(p);}

  public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
//1 确定没出现并且view不为空if (isShowing() || mContentView == null) {return;}
//2 动画TransitionManager.endTransitions(mDecorView);
//3 滚动监听registerForScrollChanged(anchor, xoff, yoff, gravity);
//4 相关值设置mIsShowing = true;mIsDropdown = true;
//5 在此位置准备弹出框final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());preparePopup(p);
//6 更新,,,final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);updateAboveAnchor(aboveAnchor);
//7 唤起弹出框invokePopup(p);}

showAtLocation()弹出框是在父控件绝对位置显示,showAsDropDown()是在相对位置出现。比较这两个方法可以发现,大致流程相似,某些细节不同。
先看此第三步骤,滚动监听。在类的开头部分有一个匿名内部类,作用为滚动监听:

//匿名内部类private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {@Overridepublic void onScrollChanged() {final View anchor = mAnchor != null ? mAnchor.get() : null;if (anchor != null && mDecorView != null) {final WindowManager.LayoutParams p = (WindowManager.LayoutParams)mDecorView.getLayoutParams();updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,mAnchoredGravity));update(p.x, p.y, -1, -1, true);}}};

然后再类中有注册监听和取消监听的私有方法:

 private void unregisterForScrollChanged() {final WeakReference<View> anchorRef = mAnchor;final View anchor = anchorRef == null ? null : anchorRef.get();if (anchor != null) {final ViewTreeObserver vto = anchor.getViewTreeObserver();vto.removeOnScrollChangedListener(mOnScrollChangedListener);}mAnchor = null;}private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {unregisterForScrollChanged();mAnchor = new WeakReference<>(anchor);final ViewTreeObserver vto = anchor.getViewTreeObserver();if (vto != null) {vto.addOnScrollChangedListener(mOnScrollChangedListener);}mAnchorXoff = xoff;mAnchorYoff = yoff;mAnchoredGravity = gravity;}

第五步骤的不同之处是在不同的位置上准备弹出框,首先获取位置p,然后调用preparePopup(p)方法准备弹出框:

 private void preparePopup(WindowManager.LayoutParams p) {if (mContentView == null || mContext == null || mWindowManager == null) {throw new IllegalStateException("You must specify a valid content view by "+ "calling setContentView() before attempting to show the popup.");}// The old decor view may be transitioning out. Make sure it finishes// and cleans up before we try to create another one.if (mDecorView != null) {mDecorView.cancelTransitions();}// When a background is available, we embed the content view within// another view that owns the background drawable.if (mBackground != null) {mBackgroundView = createBackgroundView(mContentView);mBackgroundView.setBackground(mBackground);} else {mBackgroundView = mContentView;}mDecorView = createDecorView(mBackgroundView);// The background owner should be elevated so that it casts a shadow.mBackgroundView.setElevation(mElevation);// We may wrap that in another view, so we'll need to manually specify// the surface insets.final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);p.hasManualSurfaceInsets = true;mPopupViewInitialLayoutDirectionInherited =(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);mPopupWidth = p.width;mPopupHeight = p.height;}

最大的不同在于第六步,showAtLocation的简单,比较一目了然,相比之下showAsDropDown的就比较复杂,显示调用findDropDownPosition(),然后调用updateAboveAnchor():

private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff,int yoff, int gravity) {final int anchorHeight = anchor.getHeight();final int anchorWidth = anchor.getWidth();if (mOverlapAnchor) {yoff -= anchorHeight;}anchor.getLocationInWindow(mDrawingLocation);p.x = mDrawingLocation[0] + xoff;p.y = mDrawingLocation[1] + anchorHeight + yoff;final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())& Gravity.HORIZONTAL_GRAVITY_MASK;if (hgrav == Gravity.RIGHT) {// Flip the location to align the right sides of the popup and// anchor instead of left.p.x -= mPopupWidth - anchorWidth;}boolean onTop = false;p.gravity = Gravity.LEFT | Gravity.TOP;anchor.getLocationOnScreen(mScreenLocation);final Rect displayFrame = new Rect();anchor.getWindowVisibleDisplayFrame(displayFrame);final int screenY = mScreenLocation[1] + anchorHeight + yoff;final View root = anchor.getRootView();if (screenY + mPopupHeight > displayFrame.bottom|| p.x + mPopupWidth - root.getWidth() > 0) {// If the drop down disappears at the bottom of the screen, we try// to scroll a parent scrollview or move the drop down back up on// top of the edit box.if (mAllowScrollingAnchorParent) {final int scrollX = anchor.getScrollX();final int scrollY = anchor.getScrollY();final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,scrollY + mPopupHeight + anchorHeight + yoff);anchor.requestRectangleOnScreen(r, true);}// Now we re-evaluate the space available, and decide from that// whether the pop-up will go above or below the anchor.anchor.getLocationInWindow(mDrawingLocation);p.x = mDrawingLocation[0] + xoff;p.y = mDrawingLocation[1] + anchorHeight + yoff;// Preserve the gravity adjustment.if (hgrav == Gravity.RIGHT) {p.x -= mPopupWidth - anchorWidth;}// Determine whether there is more space above or below the anchor.anchor.getLocationOnScreen(mScreenLocation);onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) <(mScreenLocation[1] - yoff - displayFrame.top);if (onTop) {p.gravity = Gravity.LEFT | Gravity.BOTTOM;p.y = root.getHeight() - mDrawingLocation[1] + yoff;} else {p.y = mDrawingLocation[1] + anchorHeight + yoff;}}if (mClipToScreen) {final int displayFrameWidth = displayFrame.right - displayFrame.left;final int right = p.x + p.width;if (right > displayFrameWidth) {p.x -= right - displayFrameWidth;}if (p.x < displayFrame.left) {p.x = displayFrame.left;p.width = Math.min(p.width, displayFrameWidth);}if (onTop) {final int popupTop = mScreenLocation[1] + yoff - mPopupHeight;if (popupTop < 0) {p.y += popupTop;}} else {p.y = Math.max(p.y, displayFrame.top);}}p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;return onTop;}private void updateAboveAnchor(boolean aboveAnchor) {if (aboveAnchor != mAboveAnchor) {mAboveAnchor = aboveAnchor;if (mBackground != null && mBackgroundView != null) {// If the background drawable provided was a StateListDrawable// with above-anchor and below-anchor states, use those.// Otherwise, rely on refreshDrawableState to do the job.if (mAboveAnchorBackgroundDrawable != null) {if (mAboveAnchor) {mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable);} else {mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable);}} else {mBackgroundView.refreshDrawableState();}}}}

第七步骤,弹出弹出框

private void invokePopup(WindowManager.LayoutParams p) {if (mContext != null) {p.packageName = mContext.getPackageName();}final PopupDecorView decorView = mDecorView;decorView.setFitsSystemWindows(mLayoutInsetDecor);setLayoutDirectionFromAnchor();mWindowManager.addView(decorView, p);if (mEnterTransition != null) {decorView.requestEnterTransition(mEnterTransition);}}private void setLayoutDirectionFromAnchor() {if (mAnchor != null) {View anchor = mAnchor.get();if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {mDecorView.setLayoutDirection(anchor.getLayoutDirection());}}}

showAtLocation和showAsDropDown就到此为止了,这个类中比较重要的方法还有dismiss(),update()等;还有比较典型的属性设置方法有setContentView(View contentView),setBackgroundDrawable(Drawable background),设置动画的方法逻辑等,在有时候自定义view时可以拿来参考。
另外,虽然PopupWindow相当于一个view,但由于它不是继承自view或其子类,所以PopupWindow类中没有onLayout(),onDraw()等方法。
这个源码读的比较粗暴,某些细节以后知识运用更熟练了再补充吧

读源码:PopupWindow相关推荐

  1. 我是怎么读源码的,授之以渔

    点击上方"视学算法",选择"设为星标" 做积极的人,而不是积极废人 作者 :youzhibing 链接 :https://www.cnblogs.com/you ...

  2. 这样读源码,不牛X也难

    程序员在工作过程中,会遇到很多需要阅读源码的场景,比如技术预研.选择技术框架.接手以前的项目.review他人的代码.维护老产品等等.可以说,阅读源代码是程序员的基本功,这项基本功是否扎实,会在很大程 ...

  3. myisam怎么读_耗时半年,我成功“逆袭”,拿下美团offer(刷面试题+读源码+项目准备)...

    欢迎关注专栏[以架构赢天下]--每天持续分享Java相关知识点 以架构赢天下​zhuanlan.zhihu.com 以架构赢天下--持续分享Java相关知识点 每篇文章首发此专栏 欢迎各路Java程序 ...

  4. 微信读书vscode插件_跟我一起读源码 – 如何阅读开源代码

    阅读是最好的老师 在学习和提升编程技术的时候,通过阅读高质量的源码,来学习专家写的高质量的代码,是一种非常有效的提升自我的方式.程序员群体是一群乐于分享的群体,因此在互联网上有大量的高质量开源项目,阅 ...

  5. 读源码,对程序员重要吗?

    来源: CSDN(ID:CSDNnews) 嘿,朋友们!本文我将分享一些关于主动阅读和研究源码的一些想法.在我看来,阅读源码能够帮你成为一名更专业的开发人员.毫无疑问的是,阅读源码提高了我的软件开发水 ...

  6. 夜读源码,带你探究 Go 语言的iota

    Go 语言的 iota 怎么说呢,感觉像枚举,又有点不像枚举,它的底层是什么样的,用哪个姿势使用才算正规,今天转载一篇「Go夜读」社区上分享的文章,咱们一起学习下.Go 夜读,带你每页读源码~!  这 ...

  7. 【一起读源码】1. Java 中元组 Tuple

    1.1 问题描述 使用 Java 做数据分析.机器学习的时候,常常需要对批量的数据进行处理,如果需要处理的数据的维度不超过10时,可以考虑使用 org.javatuples 提供的 Tuple 类工具 ...

  8. Spring读源码系列之AOP--03---aop底层基础类学习

    Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...

  9. 学会读源码,很重要!

    刚参加工作那会,没想过去读源码,更没想过去改框架的源码:总想着别人的框架应该是完美的.万能的,应该不需要改:另外即使我改了源码,怎么样让我的改动生效了?项目中引用的不还是没改的jar包吗.回想起来觉得 ...

最新文章

  1. CVPR2020论文解读:手绘草图卷积网络语义分割
  2. linux操作系统的特点有哪些,LINUX操作系统有哪些概念和特点?
  3. linux uts namespace 提供了主机名和域名的隔离 docker中被用到
  4. java操作xml文件--修改节点
  5. Scheme 语言概要
  6. GDCM:gdcm::Reader的测试程序
  7. BZOJ2806(后缀自动机+DP)
  8. 牛客网_PAT乙级_1010月饼 (25)
  9. 委托与事件-闲聊系列(二)
  10. 如何让你的手机比别人最先升级到 Android L
  11. VC++ SetLayeredWindowAttributes 部分窗口透明鼠标穿透
  12. G4L---linux系统---硬盘对拷(克隆)
  13. Logstash的logstash-input-jdbc插件mysql数据同步ElasticSearch及词库
  14. 关于java多线程堆和栈的共享问题
  15. 3个好用的3D点云数据标注工具推荐
  16. Gson解析json字符串
  17. cf英文名字格式好看的_cf好看的英语名字格式,有你想要的!
  18. VS不能使用回车键和删除键及其他键问题
  19. 管理员同志,回收站博文希望得到恢复,万分感谢
  20. iOS-高德地图API的定位与搜索功能

热门文章

  1. 京东商城商品分类列表页面
  2. 天邑ty1208z海思3798刷版本_[高安]天邑ty1208z晶晨s905lb免拆机强刷固件下载
  3. 自创计算机语言,【图片】【自创语言教程】———创造属于自己的语言!(上)【那些漫长岁月吧】_百度贴吧...
  4. 所有程序中的java在哪里设置密码_关于安全性:如何在桌面客户端应用程序(Java)中存储密码和敏感数据?...
  5. 阿里合伙人彭翼捷:每个阶段都给自己找一个目标!
  6. NovalIDE自动补全插件介绍。
  7. howland 电流源
  8. 【微信小程序】封装request以及对接口进行模块化
  9. python中可选参数如何指定_Python可选参数
  10. Flutter之声网Agora实现音频体验记录