目标
- 生成一个全局的悬浮窗,即使应用退出,悬浮窗也要保持存在,并可以保持其功能正常
- 支持自由拖动
- 动态更新内容(流量,网速等)
- 支持无需权限申请(额外)
步骤
- 构建自定义View支持自定义视图,重写onTouch() 支持自由移动,支持点击事件处理
- 构建Manager,处理悬浮窗的显示,更新,关闭等事件
- 通过Manager来调用悬浮窗的加载
实例
1. 自定义View
这里继承LinearLayout,当然可以根据需求来自定义。
/**
* 悬浮窗
*/
public class FloatWindowView extends LinearLayout {
// 小悬浮窗的宽
public int viewWidth;
// 小悬浮窗的高
public int viewHeight;
// 系统状态栏的高度
private static int statusBarHeight;
// 用于更新小悬浮窗的位置
private WindowManager windowManager;
// 小悬浮窗的布局参数
public WindowManager.LayoutParams smallWindowParams;
// 记录当前手指位置在屏幕上的横坐标
private float xInScreen;
// 记录当前手指位置在屏幕上的纵坐标
private float yInScreen;
// 记录手指按下时在屏幕上的横坐标,用来判断单击事件
private float xDownInScreen;
// 记录手指按下时在屏幕上的纵坐标,用来判断单击事件
private float yDownInScreen;
// 记录手指按下时在小悬浮窗的View上的横坐标
private float xInView;
// 记录手指按下时在小悬浮窗的View上的纵坐标
private float yInView;
// 单击接口
private OnClickListener listener;
/**
* 构造函数
* @param context
* 上下文对象
*/
public FloatWindowView(Context context) {
super(context);
windowManager = (WindowManager) MainApplication.getContext().getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(MainApplication.getContext()).inflate(R.layout.layout_test, this);
statusBarHeight = getStatusBarHeight();
TextView textView = (TextView) findViewById(R.id.window_tv);
textView.setText("悬浮窗");
smallWindowParams = new WindowManager.LayoutParams();
// 设置显示类型为phone
smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
// 显示图片格式
smallWindowParams.format = PixelFormat.RGBA_8888;
// 设置交互模式
smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 设置对齐方式为左上
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
smallWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
smallWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
smallWindowParams.x = 0;
smallWindowParams.y = 0;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
DebugLog.d("aaaa",event.getAction()+"");
switch (event.getAction()) {
// 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度
case MotionEvent.ACTION_DOWN:
// 获取相对与小悬浮窗的坐标
xInView = event.getX();
yInView = event.getY();
// 按下时的坐标位置,只记录一次
xDownInScreen = event.getRawX();
yDownInScreen = event.getRawY() - statusBarHeight;
break;
case MotionEvent.ACTION_MOVE:
// 时时的更新当前手指在屏幕上的位置
xInScreen = event.getRawX();
yInScreen = event.getRawY() - statusBarHeight;
// 手指移动的时候更新小悬浮窗的位置
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
// 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件
if (xDownInScreen - event.getRawX() < 5
&& yDownInScreen - (event.getRawY() - getStatusBarHeight()) <5) {
if (listener != null) {
listener.onClick(this);
}
}
break;
}
return true;
}
/**
* 更新悬浮窗在屏幕中的位置
*/
private void updateViewPosition() {
smallWindowParams.x = (int) (xInScreen - xInView);
smallWindowParams.y = (int) (yInScreen - yInView);
windowManager.updateViewLayout(this, smallWindowParams);
}
/**
* 设置单击事件的回调接口
*/
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
/**
* 获取状态栏的高度
* @return
*/
private int getStatusBarHeight() {
Rect frame = new Rect();
getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
return statusBarHeight;
}
}
Layout 是一个线性布局,包含了一个TextView和一个ImageView。同时预留了setOnclickLintener(),供外部传入具体的Click实现。
2.构建Manager
- 在Manager内部对自定义View进行 初始化
- 调用WindowManager 进行addView 添加到window中显示
- 传入一个ClickListener对视图进行点击监听
- 提供关闭悬浮窗方法。
/**
* 悬浮窗管理器
*
* @author zhaokaiqiang
*
*/
public class FloatWindowManager {
private FloatWindowView floatWindowView;
private WindowManager mWindowManager;
private Context context;
public FloatWindowManager(Context context) {
this.context = context;
}
/**
* 创建悬浮窗
*/
public void createFloatWindow() {
if (floatWindowView == null) {
floatWindowView = new FloatWindowView(MainApplication.getContext());
getWindowManager().addView(floatWindowView, floatWindowView.smallWindowParams);
}
}
/**
* 将小悬浮窗从屏幕上移除
**/
public void removeFloatWindow() {
if (floatWindowView != null) {
WindowManager windowManager = getWindowManager();
windowManager.removeView(floatWindowView);
floatWindowView = null;
}
}
public void setOnClickListener(View.OnClickListener listener) {
if (floatWindowView != null) {
floatWindowView.setOnClickListener(listener);
}
}
/**
* 获取WindowManager
** @return
*/
private WindowManager getWindowManager() {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
}
3.调用
首先在mainfest中添加悬浮窗权限,接着构建Manager进行炫富穿的展示。
注意在6.0以上时,对于小悬浮窗权限更为严格,需要进行动态申请,因此需要进行判断,这里只是作大致判断,更为信息请参照权限申请。
Mainfest 权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
if (Build.VERSION.SDK_INT >= 23) {
if(!Settings.canDrawOverlays(MainApplication.getContext())) {
//启动Activity让用户授权
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
return;
} else {
//执行6.0以上绘制代码
new FloatWindowManager(MainApplication.getContext()).createFloatWindow();
}
} else {
//执行6.0以下绘制代码
new FloatWindowManager(MainApplication.getContext()).createFloatWindow();
}
Tips
1.构造自定义View,super(Context context) 需要传入ApplicationContext,保证全局唯一的一个悬浮窗。
2.注意权限的申请