比较简单,主要是使用WindowManager API,以下是使用方法
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout. activity_main);
- // 获取Service
- WindowManager mWindowManager = (WindowManager) getSystemService("window" );
- ImageView imageView = new ImageView(this);
- imageView.setImageResource(R.drawable. ic_launcher);
- // 设置窗口类型,一共有三种Application windows, Sub-windows, System windows
- // API中以TYPE_开头的常量有23个
- mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT ;
- // 设置期望的bitmap格式
- mWindowParams.format = PixelFormat.RGBA_8888;
- // 以下属性在Layout Params中常见重力、坐标,宽高
- mWindowParams.gravity = Gravity.LEFT | Gravity. TOP;
- mWindowParams.x = 100;
- mWindowParams.y = 100;
- mWindowParams .width = WindowManager.LayoutParams. WRAP_CONTENT;
- mWindowParams .height = WindowManager.LayoutParams. WRAP_CONTENT;
- // 添加指定视图
- mWindowManager.addView(imageView, mWindowParams);
- }
需要添加权限
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
如果没有以上权限,会出现如下异常:
- java.lang.RuntimeException: Unable to start activity ComponentInfo{loveworld.floatview/loveworld.floatview.MainActivity}: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@40513b60 -- permission denied for this window type
- at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1768)
- at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)
- at android.app.ActivityThread.access$1500(ActivityThread.java:123)
- at android.app.ActivityThread$H.handleMessage(ActivityThread.java:939)
- at android.os.Handler.dispatchMessage(Handler.java:99)
- at android.os.Looper.loop(Looper.java:130)
- at android.app.ActivityThread.main(ActivityThread.java:3835)
- at java.lang.reflect.Method.invokeNative(Native Method)
- at java.lang.reflect.Method.invoke(Method.java:507)
- at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)
- at dalvik.system.NativeStart.main(Native Method)
- Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@40513b60 -- permission denied for this window type
- at android.view.ViewRoot.setView(ViewRoot.java:552)
- at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
- at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
- at android.view.Window$LocalWindowManager.addView(Window.java:465)
- at loveworld.floatview.MainActivity.onCreate(MainActivity.java:55)
- at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
- at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1722)
- ... 11 more
三、如何使悬浮窗口可移动
窗口随手指移动需要获取先获取手指的点击事件,而点击事件通常都是通过覆写视图的onTouchEvent获得,当前例子也看不到悬浮窗口仅能看到其中包含的图片,所以选择在ImageView获取并处理触摸事件,获取手指轨迹X,Y坐标,更新窗口位置达到随手指移动效果。
首先自定义ImageView
- public class MoveImageView extends ImageView {
- public MoveImageView(Context context) {
- super(context);
- }
- public MoveImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public MoveImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- }
覆写onTouchEvent方法,用于监听图片视图的触摸事件
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int titleHeight = 0;
- if (mListener != null) {
- titleHeight = mListener.getTitleHeight();
- }
- // 当前值以屏幕左上角为原点
- mRawX = event.getRawX();
- mRawY = event.getRawY() - titleHeight;
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- // 以当前父视图左上角为原点
- mStartX = event.getX();
- mStartY = event.getY();
- break;
- case MotionEvent.ACTION_MOVE:
- updateWindowPosition();
- break;
- case MotionEvent.ACTION_UP:
- updateWindowPosition();
- break;
- }
- // 消耗触摸事件
- return true;
- }
最后通过更新窗口X,Y轴参数达到移动效果
- /**
- * 更新窗口参数,控制浮动窗口移动
- */
- private void updateWindowPosition() {
- if (mListener != null) {
- // 更新坐标
- LayoutParams layoutParams = mListener.getLayoutParams();
- layoutParams.x = (int)(mRawX - mStartX);
- layoutParams.y = (int)(mRawY - mStartY);
- // 使参数生效
- mWindowManager.updateViewLayout(this, layoutParams);
- }
- }
其中当前自定义视图要用到两个变量,只有在Activity中可以获取到,这就涉及到如何在自定义视图中获取到这两个变量。 可以把变量保存到Application中,也可以使用单例对象在Activity中初始化并赋值在自定义视图中获取,当前是使用接口回调方法,在Activity中实现接口通过这种方法使自定义视图获取变量,感觉这种方式比较好,不用考虑把变量放到Application涉及到的生命周期释放等问题,也不用考虑单例促使对象的生命周期一样很长。
先来看看在自定义视图中如何定义
- /**
- * 设置监听器,用于向当前ImageView传递参数
- *
- * @param listener
- */
- public void setFloatViewParamsListener(FloatViewParamsListener listener) {
- mListener = listener;
- }
- /**
- * 当前视图用于获取参数
- */
- public interface FloatViewParamsListener {
- /**
- * 获取标题栏高度
- * 因为需要通过Window对象获取,所以使用此办法
- *
- * @return
- */
- public int getTitleHeight();
- /**
- * 获取当前WindowManager.LayoutParams 对象
- *
- * @return
- */
- public WindowManager.LayoutParams getLayoutParams();
- }
在Activity中实现接口并设置
- private class FloatViewListener implements FloatViewParamsListener {
- @Override
- public int getTitleHeight() {
- // 获取状态栏高度。不能在onCreate回调方法中获取
- Rect frame = new Rect();
- getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
- int statusBarHeight = frame.top;
- int contentTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
- int titleBarHeight = contentTop - statusBarHeight;
- return titleBarHeight;
- }
- @Override
- public android.view.WindowManager.LayoutParams getLayoutParams() {
- return mWindowParams;
- }
- }
记得在退出Activity时清理悬浮窗口
- @Override
- public void onDestroy(){
- super.onDestroy();
- // 删除视图
- mWindowManager.removeView(mImageView);
- }