已阅读:11,924 次
Android使用WindowManager实现PopupWindow浮动层窗口
ian | Android | 2012/07/25


有些时候我们的APP需要在当前屏幕上显示一些提示信息,当然大多数情况下可以通过比如对话框、Activity来完成,但是还是会有一些特殊的场景,比如不能使当前Activity Pause、要始终置于屏幕最前面等,这个时候我们可能就需要使用WindowManager来添加一个浮动层View。浮动层有很多现成的使用场景,比如鼠标、Toast和Dialog都是通过这样的方式实现的,因此你可以在任何时刻弹出一个Toast而不会对当前窗口产生任何影响,更复杂一些的使用场景是系统的音量调节UI、关机对话框等。

看到这里大致了解了浮动层的使用场景,之前的项目中在焦点移动动画、全局Push消息提示中采用了这样的方案,因此使用到了PopupWindow,下面简单介绍一些如何实现。

浮动层的实现主要使用到了WindowManager这个类,我们可以向这个类任意添加一个自定义的View,并且可以控制View从而实现一些交互和动画特效。下面介绍一个简单的PopupWindow的代码实现:

TipsPopupWindow.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public class TipsPopupWindow {
 
    private FrameLayout popWindowWrap;
 
    private View animationControler;
 
    private View popView;
 
    private Context mContext;
 
    TipsPopupWindow(Context context,WindowManager.LayoutParams popupWindowLayoutParams) {
        mContext = context;
        popWindowWrap = new FrameLayout(mContext);
        LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        popView = layoutInflater.inflate(R.layout.session_tips_prompt, null);
        popWindowWrap.setLayoutParams(popupWindowLayoutParams);
        popWindowWrap.addView(popView);
        WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        mWindowManager.addView(popWindowWrap, popWindowWrap.getLayoutParams());
        animationControler = popWindowWrap.findViewById(R.id.tips_popwindowframe);
    }
 
    public void startEnterAnimation() {
        animationControler.post(new Runnable() {
 
            @Override
            public void run() {
 
                Rect rect = new Rect();
                animationControler.getGlobalVisibleRect(rect);
                final int[] location1 = new int[2];
                animationControler.getLocationInWindow(location1);
                TranslateAnimation enterAnimation = new TranslateAnimation(location1[0], location1[0], location1[1]
                        + animationControler.getHeight(), location1[1]);
                enterAnimation.setDuration(1);
 
                enterAnimation.setAnimationListener(new AnimationListener() {
 
                    @Override
                    public void onAnimationStart(Animation animation) {
 
                    }
 
                    @Override
                    public void onAnimationEnd(Animation animation) {
                    }
 
                    @Override
                    public void onAnimationRepeat(Animation animation) {
 
                    }
                });
                animationControler.startAnimation(enterAnimation);
            }
        });
    }
 
    private void initDisappearAnimation(final int[] location1, int delayTime) {
        final TranslateAnimation displayAnimation = new TranslateAnimation(location1[0], location1[0], location1[1],
                location1[1] + animationControler.getHeight());
        displayAnimation.setDuration(1);
 
        displayAnimation.setAnimationListener(new AnimationListener() {
 
            @Override
            public void onAnimationStart(Animation animation) {
 
            }
 
            @Override
            public void onAnimationEnd(Animation animation) {
                WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
                mWindowManager.removeView(popWindowWrap);
            }
 
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        animationControler.postDelayed(new Runnable() {
            @Override
            public void run() {
                animationControler.startAnimation(displayAnimation);
            }
        }, delayTime);
    }
 
    // 打开关闭Camera
    public void onOpenCamera(final boolean isOpen) {
    }
 
    // 静音设置
    public void onSetMute(final boolean isMute) {
    }
 
    /**
     * 让Tips从Window中立即滑出
     */
    public void revomeFromWindow() {
        final int[] location = new int[2];
        animationControler.getLocationInWindow(location);
        initDisappearAnimation(location, 0);
    }
 
    public void setVisiable(boolean visiable){
        popView.setVisibility(visiable ? View.VISIBLE : View.GONE);
    }
}

TipsPopupWindow的内容是一个提示性质的Tips,从接口可以看出是对Camera和麦克风的状态进行提示的View,这里我把跟具体功能相关的代码去掉了,下面是如何使用这个类的调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mPopupWindowLayoutParams = new WindowManager.LayoutParams();
mPopupWindowLayoutParams.gravity = Gravity.BOTTOM;
mPopupWindowLayoutParams.flags = mPopupWindowLayoutParams.flags
        | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES
        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
mPopupWindowLayoutParams.width = WindowManager.LayoutParams.FILL_PARENT;
mPopupWindowLayoutParams.height = 100;
mPopupWindowLayoutParams.format = android.graphics.PixelFormat.TRANSLUCENT;
mPopupWindowLayoutParams.type = android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mPopupWindowLayoutParams.token = null;
 
mTipsPopWindowView = new TipsPopupWindow(mContext, mPopupWindowLayoutParams);
mTipsPopWindowView.startEnterAnimation();

这样就能显示这个PopupWindow了,这个View会始终显示在屏幕的最上方而不会被覆盖,需要让其消失的话调用revomeFromWindow即可。

需要注意的地方
上面的代码在使用过程中发现了一个问题,因为项目的其他地方有弹出对话框和Toast,当显示了TipsPopWindowView之后,发现Toast可以正常显示、但是Dialog却无法显示的问题,后来查看Dialog和Toast的源码以及相关文档之后,发现是因为不同Window类型的图层顺序,导致TipsPopWindowView挡住了Dialog。

关于Window的类型,主要有三种:
1 Application Windows:取值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间。
是通常的、顶层的应用程序窗口。必须将token设置成activity的token。
2 Sub Windows:取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间。
与顶层窗口相关联,token必须设置为它所附着的宿主窗口的token。
3 System Windows:取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之间。
用于特定的系统功能。它不能用于应用程序,使用时需要特殊权限,在manifest.xml中添加如下声明:

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

这三种类型的图层顺序是一次增高,即Application Windows在对底层,System Windows在最上层。看到这里我们再来看一下上面的代码,其中这样一句:

1
mPopupWindowLayoutParams.type = android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;

这样我们的TipsPopWindowView变成了最上层,而Dialog是属于Sub Windows类型的,Toast是System Windows类型,因此Toast可见,而Dialog被覆盖。这里我们视具体情况改为对应的Type即可。

原创文章,转载请注明:转载自ian的个人博客[http://www.icodelogic.com]
本文链接地址: http://www.icodelogic.com/?p=625

tags:

2条评论

  1. 很多东西都挺实用呢

  2. 高级豕 说:

    说的甚有道理,但总觉得有点偏激。

发表评论

你需要先 登录 才能回复