用WindowManager 实现悬浮窗小动画
前言
先看效果图,添加悬浮窗,悬浮窗可以移动,并且可以用于桌面,这里用到了 WindowManager 以及 GestureDetector;没有对点击事件进行处理;
Window 与 WindowManager
-
伪代码使用WindowManager 来添加一个 Window 。
WindowManager manager= (WindowManager) getSystemService(Context .WINDOWSERVICE); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width,height,type,flags,format); manager . addView(btn, layoutParams) ;
-
flag 参数意义
flags 用来控制Window 的显示特性;
public static final int FLAG NOT FOCUSABLE = 0x00000008;
表示此 Window 不需要获取焦点,不接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH MODAL,最终事件会直接传递给下层具有焦点的 Window 。public stat 工c final int FLAG NOT TOUCH MODAL = 0x00000020 ;
自己 Window 区域内的事件自己处理:自己 Window 区域外的事件传递给底层Window 处理。一般这个选项会默认开启, 否则其他Window 无法收到事件。public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
可以让此Window 显示在锁屏上。 -
type 参数意义
type 参数表示Window 的类型。Window 有三种类型:应用Window 、子Window 和系统Window 。
应用Window 对应着一个Activity 。子Window 不能独立存在,它需要附属在特定的父Window 中,比如Dialog 就是一个子Window 。
系统Window 是需要声明权限才能创建的,比如Toast 和系统状态栏都是系统Window 。
Window 是分层的,层级大的Window 会覆盖在层级小的Window 上面。- 应用Window 的层级范围:1 ~99
- 子Window 的层级范围:1000~ 1999
- 系统Window 的层级范围:2000~29990
如果想让 Window 置于顶层,则采用较大的层级即可;如果
是系统类型的 Window ,则需要在 AndroidMenifest.xml 中配置如下权限声明,否则会报权限不足的错误。<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
-
Window Manager 提供的常用的只有三个方法,即添加View 、更新 VieW 和删除 View 。这三个方法定义在ViewManager 中,而Window Manager 继承自 ViewManager 。
public interface WindowManager extends ViewManager { public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params) ; public void updateViewLayout(View view , ViewGroup . LayoutParams params) ; public void removeView(View view) ; } }
实例
-
添加布局,两个 button,添加、移除;
-
在 onCreate() 方法中初始化:
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_float_windows); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //参考:https://blog.youkuaiyun.com/zxm317122667/article/details/52685492 if (!Settings.canDrawOverlays(this)) { Intent mIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); mIntent.setData(Uri.parse("package:" + getPackageName())); mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivityForResult(mIntent, REQUESTCODE); } else { initView(); } } else { initView(); } } private void initView() { //初始化布局,并添加点击事件监听 …… mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE); }
注意:
- 在 SDKAPI ≥ 23时,需要在代码中动态判断版本,并通过
Settings.ACTION_MANAGE_OVERLAY_PERMISSION
申请; - 通过
Settings.canDrawOverlays(this)
判断设备是否可以显示,避免每次打开都要去验证权限;该方法自参考:https://blog.youkuaiyun.com/zxm317122667/article/details/52685492;
- 在 SDKAPI ≥ 23时,需要在代码中动态判断版本,并通过
-
添加点击事件
@Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_add_float_win: if (iconCount > 0) { //只填加一个 break; } mImageView = new ImageView(this); mImageView.setBackgroundResource(R.drawable.icon52); //设置图片尺寸:WindowManager.LayoutParams.WRAP_CONTENT, //WindowManager.LayoutParams.WRAP_CONTENT, mLayoutParams = new WindowManager.LayoutParams( 150, 150, 2003, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSPARENT); mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; mLayoutParams.x = 200; mLayoutParams.y = 800; mWindowManager.addView(mImageView, mLayoutParams); mImageView.setOnTouchListener(this); iconCount++; break; case R.id.btn_remove_float_win: if (iconCount < 1) { break; } mWindowManager.removeViewImmediate(mImageView); iconCount--; break; default: break; } }
注意:
- 这里添加了
mImageView.setOnTouchListener(this);
应将 Activity 实现View.OnTouchListener
;需要重写 onTouch() 方法; - 在添加 Window 时,其实我们只是将 ImageView 利用 WindowManager 的
LayoutParams 添加到 Window Manager 中,但 Window 只是一个虚拟概念,真正添加到Window Manager 中的其实是 View 。
重写 onTouch() 方法
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = rawX - mImageView.getWidth() / 2;
mLayoutParams.y = rawY - mImageView.getHeight();
mWindowManager.updateViewLayout(mImageView, mLayoutParams);
break;
default:
break;
}
return false;
}
注:这里让 ImageView 随手指移动;使用了 如下代码;不要问我为什么上面减一半下面减去整个,因为不这样会出现不同步现象;别问,问就不会……
mLayoutParams.x = rawX - mImageView.getWidth() / 2;
mLayoutParams.y = rawY - mImageView.getHeight();
最后,附上悬浮窗的原图: