android 获得顶层窗口_Android使用WindowManager实现PopupWindow浮动层窗口

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

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

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

TipsPopupWindow.java

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,这里我把跟具体功能相关的代码去掉了,下面是如何使用这个类的调用方法:

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中添加如下声明:

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

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

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


版权声明:本文为weixin_42509931原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。