PopupWindow应用的场景还是蛮多的。显示在当前activity界面之上的任意弹出窗口基本上都可以用PopupWindow来实现。因为它可以显示任意的View,完全由你定制,而且显示位置也可以灵活控制。

这里回顾一下PopupWindow用法,并通过源码更深层了解PopupWindow。

官方文档:

https://developer.android.com/reference/android/widget/PopupWindow.html

PopupWindow并不是继承Window


PopupWindow的作用的是显示用户自己定义的View。它显示是一个纯净的View,怎么说呢,它与activity的对应的界面不一样,activity封装很多对象,比如Window,DecorView.而PopupWindow显是就是一个View,这涉及到了应用窗口,子窗口,和系统窗口关于android 窗口类型的问题,这里就不详聊了,先知道,显示的顺序是系统窗口(比如状态栏,Toast)会显示在最上面,其次是子窗口(例如popupwindow),然后是应用窗口(activity对应的窗口)。对于系统按键比如back 键 一般窗口的View都没有进行处理,如果想处理,就需要设置key的相关监听器,应用程序窗口中的View没有处理key事件的话,就会交给activity去处理,结果按back 键退出应用,而像子窗口(popupwindow)没有window,activity这些东西包装,所以默认情况就对key事件无效了,这涉及到android key事件的分发,前面我曾分析过源码分析Android触摸事件处理机制,按键事件和触摸事件的处理规则差不多,也是递归处理过程。这里不详聊。但是PopupWindow如果设置了setBackgroundDrawable的话,就会针对back按键进行,后面会分析到。

不管怎样,首先用户准备好自己要显示内容。比如现在需要显示的界面如下:

<?xml version="1.0" encoding="utf-8"?>            

就简单几个控件。将资源文件渲染成View。

popupView = getLayoutInflater().inflate(R.layout.popup_layout,null);

准备好了需要显示的内容之后,等于完成了一半,接下就是创建PopupWindow,将该view传给它,让后让它显示。

看一下PopupWindow构造函数:



构造函数蛮多的,可以根据有没有View参数来看,没有 View参数的(就是需要显示的内容视图)的构造函数创建的PopuWindow对象,肯定还需要调用的它的setContentView(View contentView)函数将View设置好。

现在以最后一个构造函数来创建PopuWindow

popupWindow  = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT,true);

第1个参数就是View了,第2,3个参数分别是宽度和高度 ,可以传具体值,这些也可以通过 setHeight (int height) setWidth(int width)传入,第4个参数很重要,看意思是是否能获取焦点。

如果focusable为false,代表该PopupWindow显示的窗口不能获取焦点,假如该界面有一个EditText控件,打开界面时,就会这样:


这过程我尝试输入内容,或按退格键都无任何反应。因为没有焦点

这时点击手机的back键,直接退出应用了.相当于焦点都获取不到,key事件肯定也不会传给你处理,给系统处理了,将应用退出。因为PopupWindow确实会处理back键事件,前提是调用了下面的函数:

popupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));

这个函数也很重要,不仅PopupWindow能否处理 back key事件与它有关,而且点击PopupWindow界面之外的地方,PopupWindow会消失也与它有关,后面会讲到。

在这里由于设置了facusable为false,是否设置setBackgroundDrawable函数也无意义。

看一下facusable为true的正常情况:



所以一般设置facusable为true。

接来设置setBackgroundDrawable(),假如不设置backgroud了,

然后直接调用:

popupWindow.showAtLocation(view,Gravity.CENTER, 0, 0);

显示 的popup,除了发现显示窗口没有背景之外,点击back键也无任何反应.

接下就通过源码看一下设置backgroud的作用,以及显示popup的过程:

在frameworks/base/core/java/android/widget/PopupWindow.java文件中

看一下showAtLocation函数:

public void showAtLocation(View parent, int gravity, int x, int y) {        showAtLocation(parent.getWindowToken(), gravity, x, y);    }

函数作用显示popup在什么位置,依据那个view来,接着往下看:

  public void showAtLocation(IBinder token, int gravity, int x, int y) {        if (isShowing() || mContentView == null) {            return;        }        unregisterForScrollChanged();        mIsShowing = true;        mIsDropdown = false;        WindowManager.LayoutParams p = createPopupLayout(token);        p.windowAnimations = computeAnimationResource();               preparePopup(p);        if (gravity == Gravity.NO_GRAVITY) {            gravity = Gravity.TOP | Gravity.START;        }        p.gravity = gravity;        p.x = x;        p.y = y;        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;        invokePopup(p);    }

WindowManager.LayoutParams p = createPopupLayout(token);
创建布局的参数

p.windowAnimations = computeAnimationResource();

popup进入动画和退出动画

下面看preparePopup()这个函数,这个函数涉及到了backgroud了

 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.");        }        if (mBackground != null) {            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();            int height = ViewGroup.LayoutParams.MATCH_PARENT;            if (layoutParams != null &&                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {                height = ViewGroup.LayoutParams.WRAP_CONTENT;            }            // when a background is available, we embed the content view            // within another view that owns the background drawable            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(                    ViewGroup.LayoutParams.MATCH_PARENT, height            );            popupViewContainer.setBackgroundDrawable(mBackground);            popupViewContainer.addView(mContentView, listParams);            mPopupView = popupViewContainer;        } else {            mPopupView = mContentView;        }        mPopupViewInitialLayoutDirectionInherited =                (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);        mPopupWidth = p.width;        mPopupHeight = p.height;    }

mContentView就是需要显示的内容了,先通过构造函数和设置函数设置好了。

mBackground就是通过setBackgroundDrawable()函数设置进去的。

来看一下如果设置了mBackground,就创建一个容器PopupViewContainer,然后将用户要显示的内容添加到这个容器里,让后显示这个容器,从而就将我们的内容现出来了,来看一下PopupViewContainer:

private class PopupViewContainer extends FrameLayout {        private static final String TAG = "PopupWindow.PopupViewContainer";        public PopupViewContainer(Context context) {            super(context);        }        @Override        protected int[] onCreateDrawableState(int extraSpace) {            if (mAboveAnchor) {                // 1 more needed for the above anchor state                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);                return drawableState;            } else {                return super.onCreateDrawableState(extraSpace);            }        }        @Override        public boolean dispatchKeyEvent(KeyEvent event) {            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {                if (getKeyDispatcherState() == null) {                    return super.dispatchKeyEvent(event);                }                if (event.getAction() == KeyEvent.ACTION_DOWN                        && event.getRepeatCount() == 0) {                    KeyEvent.DispatcherState state = getKeyDispatcherState();                    if (state != null) {                        state.startTracking(event, this);                    }                    return true;                } else if (event.getAction() == KeyEvent.ACTION_UP) {                    KeyEvent.DispatcherState state = getKeyDispatcherState();                    if (state != null && state.isTracking(event) && !event.isCanceled()) {                        dismiss();                        return true;                    }                }                return super.dispatchKeyEvent(event);            } else {                return super.dispatchKeyEvent(event);            }        }        @Override        public boolean dispatchTouchEvent(MotionEvent ev) {            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {                return true;            }            return super.dispatchTouchEvent(ev);        }        @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);            }        }        @Override        public void sendAccessibilityEvent(int eventType) {            // clinets are interested in the content not the container, make it event source            if (mContentView != null) {                mContentView.sendAccessibilityEvent(eventType);            } else {                super.sendAccessibilityEvent(eventType);            }        }    }    

它继承FrameLayout,看到没它重载了默认的处理的key函数,增加了对back按键的处理。也是为什么poupwindow显示的时候,popupwidnow消失,而应用不退出的原因。还有就是当前在popup之外的地方时,popup也会消失,是在这里触摸事件中处理的。

因此setFocusable(boolean focusable)要设成true,setBackgroundDrawable(Drawable background)必须设置。才能保证popup基本功能正常。

如果没有backgroud,直接显示就是我传进去的view了,默认这view是不处理key事件的,除非你设置key的相关监听器了,在监听器的相关回调里自己处理。

关于PopupWindow两个关键的点讲了,下面还有一下其他Api,提一下:


setTouchable(boolean touchable)

设置popup是否能接收触摸事件:



现在popup上两个textView都是都是可点击的,设置touchable为true,点击两个textview:

@Override    public void onClick(View v) {        switch (v.getId()){            case R.id.select:                showPopup(v);            break;            case R.id.photo:                Log.d(tag,"select photo");                break;            case R.id.vedio:                Log.d(tag,"select vedio");                break;        }    }

D/popupwindow: select photoD/popupwindow: select vedio

设置为false的话,就没反应了。


setAnimationStyle(int animationStyle) 

 设置popup进入和消失的动画,这个在资源文件配置一下:


popup_enter.xml

        
popup_exit.xml

        

 popupWindow.setAnimationStyle(R.style.PopupAnimation);

看效果:



下面再看一下从底部弹出popup:

修改进入动画和退出动画:

enter.xml

<?xml version="1.0" encoding="utf-8"?>

out.xml

<?xml version="1.0" encoding="utf-8"?>

同时修改一下popup显示的位置的:

popupWindow.showAtLocation(view,Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM, 0, 0);

看一下效果:




因为之前用过,不怎么记得了,现在有时间就记录一下笔记,以后用到翻一翻。

更多相关文章

  1. 一步一步学android OpenGL ES2.0编程(1)
  2. Android(安卓)开发之RecyclerView的使用
  3. android adb shell 修改权限
  4. Android(安卓)ApiDemos示例解析(112):Views->Expandable Lists->
  5. 如何在代码中动态设置字体大小
  6. Android通信方式(一)————WebView
  7. wpa_supplicant适配层 -- 详解
  8. Android(安卓)检测网络连接状态
  9. android学习笔记(八)

随机推荐

  1. android makefile message output
  2. Gradle: The New Android(安卓)Build Sys
  3. android:file
  4. Android如何引用其他工程
  5. 【Mark】Android(安卓)Basic_Activity Em
  6. android 对话框大全
  7. android 五种布局模式
  8. Android(安卓)视频录制
  9. Android设置文本框单行多行显示
  10. android短信demo