目标
本篇博客的目标主要有三个
- 不使用安卓原生控件,比如PopupWindow,Dialog等控件,实现我们的目标
- PopupWindow用于在Activity中全屏展示,模态,PopupWindow下面的全部内容不可操作,当Activity隐藏时,PopupWindow也随之隐藏
- SystemAlertDialog用于在整个系统中悬浮展示,非模态,SystemAlertDialog下面的内容仍然可以操作,当Activity隐藏时,SystemAlertDialog并不隐藏,它可以在系统桌面之上显示
注意,桌面悬浮框是需要申请额外权限的,需要在清单里注册"SYSTEM_ALERT_WINDOW"权限,并在应用设置里开启"允许在应用上层显示"功能
另外,默认情况下,Window中的ContentView,是不具备处理按键事件的功能的,如果我们需要监听返回键时关闭Window,需要自己重写一个ContentView
实现代码
package com.android.architecture;
import android.view.KeyEvent;
public interface IKeyEventDispatcher {
boolean dispatchKeyEvent(KeyEvent event);
void setKeyEventListener(KeyEventListener listener);
interface KeyEventListener {
boolean dispatchKeyEvent(KeyEvent event);
}
}
package com.android.architecture;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.FrameLayout;
public class KeyEventFrameLayout extends FrameLayout implements IKeyEventDispatcher {
KeyEventListener listener;
public KeyEventFrameLayout(Context context) {
super(context);
}
public KeyEventFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public KeyEventFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setKeyEventListener(KeyEventListener listener) {
this.listener = listener;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (listener == null)
return super.dispatchKeyEvent(event);
return listener.dispatchKeyEvent(event);
}
}
//显示应用内弹窗
protected void showPopupWindow() {
WindowManager windowManager = StartActivity.this.getSystemService(WindowManager.class);
//设置window大小
WindowManager.LayoutParams lpContent = new WindowManager.LayoutParams();
lpContent.height = ViewGroup.LayoutParams.MATCH_PARENT;
lpContent.width = ViewGroup.LayoutParams.MATCH_PARENT;
lpContent.format = PixelFormat.RGBA_8888;
lpContent.gravity = Gravity.CENTER;
//设置window类型,这个同时也决定了window显示的优先级
lpContent.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//设置窗体特性
lpContent.flags = 0;
lpContent.flags = lpContent.flags | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
lpContent.flags = lpContent.flags | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
//设置背景变暗程度
lpContent.dimAmount = 0.6F;
//创建ContentView
KeyEventFrameLayout contentView = new KeyEventFrameLayout(StartActivity.this);
//添加内容
TextView textView = new TextView(StartActivity.this);
textView.setText("Popup Window");
textView.setTextColor(Color.WHITE);
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 30);
FrameLayout.LayoutParams lpChild = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lpChild.gravity = Gravity.CENTER;
textView.setLayoutParams(lpChild);
contentView.addView(textView);
//监听返回事件
contentView.setKeyEventListener(event -> {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
windowManager.removeView(contentView);
return true;
}
return false;
});
//监听点击事件
contentView.setOnClickListener(v -> {
windowManager.removeView(contentView);
});
//显示window
windowManager.addView(contentView, lpContent);
}
//显示系统悬浮框
//注意,这个操作需要注册"SYSTEM_ALERT_WINDOW"权限,并开启"允许在应用上层显示"功能
protected void showSystemAlertDialog() {
WindowManager windowManager = StartActivity.this.getSystemService(WindowManager.class);
//设置window大小
WindowManager.LayoutParams lpContent = new WindowManager.LayoutParams();
lpContent.height = 400;
lpContent.width = 400;
lpContent.format = PixelFormat.RGBA_8888;
lpContent.gravity = Gravity.TOP | Gravity.RIGHT;
//设置window类型,这个同时也决定了window显示的优先级
lpContent.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
//设置窗体特性
lpContent.flags = 0;
lpContent.flags = lpContent.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//创建ContentView
//由于原生的FrameLayout不能监听返回键,我们必须重写一个自己的FrameLayout
KeyEventFrameLayout contentView = new KeyEventFrameLayout(StartActivity.this);
//添加内容
TextView textView = new TextView(StartActivity.this);
textView.setText("System Alert Dialog");
textView.setTextColor(Color.WHITE);
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 30);
FrameLayout.LayoutParams lpChild = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lpChild.gravity = Gravity.CENTER;
textView.setLayoutParams(lpChild);
contentView.addView(textView);
//监听返回事件
contentView.setKeyEventListener(event -> {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
windowManager.removeView(contentView);
return true;
}
return false;
});
//显示window
windowManager.addView(contentView, lpContent);
}
WindowManager中常见Flag的作用
//无法获得焦点,即无法接收到任何KeyEvent事件
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//无法触摸,即无法接收到任何MotionEvent
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
//非模态,即不阻挡底部内容
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//背景变暗
WindowManager.LayoutParams.FLAG_DIM_BEHIND;
//全屏,即不显示状态栏和导航栏背景
WindowManager.LayoutParams.FLAG_FULLSCREEN;
//绘制状态栏和导航栏,如果不绘制,可能会出现导航栏空白现象
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;