Android Window 二 可移动悬浮窗口 WindowManager

来源:互联网 发布:淘宝顶级卖家年收入 编辑:程序博客网 时间:2024/05/18 01:20
一、效果演示



二、如何创建悬浮窗口

     比较简单,主要是使用WindowManager API,以下是使用方法

[java] view plain copy
 print?
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout. activity_main);  
  5.      
  6.     // 获取Service  
  7.     WindowManager mWindowManager = (WindowManager) getSystemService("window" );  
  8.      
  9.     ImageView imageView = new ImageView(this);  
  10.     imageView.setImageResource(R.drawable. ic_launcher);  
  11.      
  12.     // 设置窗口类型,一共有三种Application windows, Sub-windows, System windows  
  13.     // API中以TYPE_开头的常量有23个  
  14.     mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT ;  
  15.     // 设置期望的bitmap格式  
  16.     mWindowParams.format = PixelFormat.RGBA_8888;  
  17.      
  18.     // 以下属性在Layout Params中常见重力、坐标,宽高  
  19.     mWindowParams.gravity = Gravity.LEFT | Gravity. TOP;  
  20.     mWindowParams.x = 100;  
  21.     mWindowParams.y = 100;  
  22.      
  23.     mWindowParams .width = WindowManager.LayoutParams. WRAP_CONTENT;  
  24.     mWindowParams .height = WindowManager.LayoutParams. WRAP_CONTENT;  
  25.      
  26.     // 添加指定视图  
  27.     mWindowManager.addView(imageView, mWindowParams);  
  28. }  


需要添加权限

[java] view plain copy
 print?
  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  

如果没有以上权限,会出现如下异常:

[java] view plain copy
 print?
  1. 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  
  2.      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1768)  
  3.      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)  
  4.      at android.app.ActivityThread.access$1500(ActivityThread.java:123)  
  5.      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:939)  
  6.      at android.os.Handler.dispatchMessage(Handler.java:99)  
  7.      at android.os.Looper.loop(Looper.java:130)  
  8.      at android.app.ActivityThread.main(ActivityThread.java:3835)  
  9.      at java.lang.reflect.Method.invokeNative(Native Method)  
  10.      at java.lang.reflect.Method.invoke(Method.java:507)  
  11.      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)  
  12.      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)  
  13.      at dalvik.system.NativeStart.main(Native Method)  
  14. Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@40513b60 -- permission denied for this window type  
  15.      at android.view.ViewRoot.setView(ViewRoot.java:552)  
  16.      at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)  
  17.      at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)  
  18.      at android.view.Window$LocalWindowManager.addView(Window.java:465)  
  19.      at loveworld.floatview.MainActivity.onCreate(MainActivity.java:55)  
  20.      at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
  21.      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1722)  
  22.      ... 11 more  



三、如何使悬浮窗口可移动

     窗口随手指移动需要获取先获取手指的点击事件,而点击事件通常都是通过覆写视图的onTouchEvent获得,当前例子也看不到悬浮窗口仅能看到其中包含的图片,所以选择在ImageView获取并处理触摸事件,获取手指轨迹X,Y坐标,更新窗口位置达到随手指移动效果。

     首先自定义ImageView

[java] view plain copy
 print?
  1. public class MoveImageView extends ImageView {  
  2.   
  3.     public MoveImageView(Context context) {  
  4.         super(context);  
  5.     }  
  6.   
  7.     public MoveImageView(Context context, AttributeSet attrs) {  
  8.         super(context, attrs);  
  9.     }  
  10.   
  11.     public MoveImageView(Context context, AttributeSet attrs, int defStyle) {  
  12.         super(context, attrs, defStyle);  
  13.     }  
  14.   
  15.       
  16. }  

覆写onTouchEvent方法,用于监听图片视图的触摸事件

[java] view plain copy
 print?
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent event) {  
  3.   
  4.     int titleHeight = 0;  
  5.     if (mListener != null) {  
  6.         titleHeight = mListener.getTitleHeight();  
  7.     }  
  8.       
  9.     // 当前值以屏幕左上角为原点  
  10.     mRawX = event.getRawX();  
  11.     mRawY = event.getRawY() - titleHeight;  
  12.       
  13.     final int action = event.getAction();  
  14.       
  15.     switch (action) {  
  16.     case MotionEvent.ACTION_DOWN:  
  17.         // 以当前父视图左上角为原点  
  18.         mStartX = event.getX();  
  19.         mStartY = event.getY();  
  20.           
  21.         break;  
  22.   
  23.     case MotionEvent.ACTION_MOVE:  
  24.         updateWindowPosition();  
  25.         break;  
  26.           
  27.     case MotionEvent.ACTION_UP:  
  28.         updateWindowPosition();  
  29.         break;    
  30.     }  
  31.       
  32.     // 消耗触摸事件  
  33.     return true;  
  34. }  

最后通过更新窗口X,Y轴参数达到移动效果

[java] view plain copy
 print?
  1. /** 
  2.  * 更新窗口参数,控制浮动窗口移动 
  3.  */  
  4. private void updateWindowPosition() {  
  5.     if (mListener != null) {  
  6.         // 更新坐标  
  7.         LayoutParams layoutParams = mListener.getLayoutParams();  
  8.         layoutParams.x = (int)(mRawX - mStartX);  
  9.         layoutParams.y = (int)(mRawY - mStartY);  
  10.           
  11.         // 使参数生效  
  12.         mWindowManager.updateViewLayout(this, layoutParams);  
  13.     }  
  14. }  

    其中当前自定义视图要用到两个变量,只有在Activity中可以获取到,这就涉及到如何在自定义视图中获取到这两个变量。 可以把变量保存到Application中,也可以使用单例对象在Activity中初始化并赋值在自定义视图中获取,当前是使用接口回调方法,在Activity中实现接口通过这种方法使自定义视图获取变量,感觉这种方式比较好,不用考虑把变量放到Application涉及到的生命周期释放等问题,也不用考虑单例促使对象的生命周期一样很长。

    先来看看在自定义视图中如何定义

[java] view plain copy
 print?
  1. /** 
  2.  * 设置监听器,用于向当前ImageView传递参数 
  3.  *  
  4.  * @param listener 
  5.  */  
  6. public void setFloatViewParamsListener(FloatViewParamsListener listener) {  
  7.     mListener = listener;  
  8. }  
  9.   
  10. /** 
  11.  *  当前视图用于获取参数 
  12.  */  
  13. public interface FloatViewParamsListener {  
  14.       
  15.     /** 
  16.      * 获取标题栏高度 
  17.      *      因为需要通过Window对象获取,所以使用此办法 
  18.      *  
  19.      * @return 
  20.      */  
  21.     public int getTitleHeight();  
  22.       
  23.       
  24.     /** 
  25.      * 获取当前WindowManager.LayoutParams 对象 
  26.      *  
  27.      * @return 
  28.      */  
  29.     public WindowManager.LayoutParams getLayoutParams();  
  30. }  

    在Activity中实现接口并设置

[java] view plain copy
 print?
  1. private class FloatViewListener implements FloatViewParamsListener {  
  2.     @Override  
  3.     public int getTitleHeight() {  
  4.         // 获取状态栏高度。不能在onCreate回调方法中获取  
  5.         Rect frame = new Rect();  
  6.         getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);  
  7.         int statusBarHeight = frame.top;   
  8.           
  9.         int contentTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();  
  10.         int titleBarHeight = contentTop - statusBarHeight;  
  11.           
  12.         return titleBarHeight;  
  13.     }  
  14.   
  15.     @Override  
  16.     public android.view.WindowManager.LayoutParams getLayoutParams() {  
  17.         return mWindowParams;  
  18.     }     
  19. }  


记得在退出Activity时清理悬浮窗口

[java] view plain copy
 print?
  1. @Override  
  2. public void onDestroy(){  
  3.     super.onDestroy();  
  4.     //  删除视图  
  5.     mWindowManager.removeView(mImageView);  
  6. }  



四、参考资料

WindowManager ---implements---> ViewManager

WindowManager  API


WindowManager.LayoutParams type

Android中可自由移动悬浮窗口的Demo


五、 下载源码

点击下载


待补充 : 参数 mWindowParams.flags