有些时候我们的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即可。