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

1. @Override  
2. protected void onCreate(Bundle savedInstanceState) {  
3. super.onCreate(savedInstanceState);  
4.     setContentView(R.layout. activity_main);  
5.      
6. // 获取Service  
7. "window" );  
8.      
9. 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. 100;  
21. 100;  
22.      
23.     mWindowParams .width = WindowManager.LayoutParams. WRAP_CONTENT;  
24.     mWindowParams .height = WindowManager.LayoutParams. WRAP_CONTENT;  
25.      
26. // 添加指定视图  
27.     mWindowManager.addView(imageView, mWindowParams);  
28. }


需要添加权限

1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />



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

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. 1768)  
3. 1784)  
4. 1500(ActivityThread.java:123)  
5. 939)  
6. 99)  
7. 130)  
8. 3835)  
9.      at java.lang.reflect.Method.invokeNative(Native Method)  
10. 507)  
11. 847)  
12. 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. 552)  
16. 177)  
17. 91)  
18. 465)  
19. 55)  
20. 1047)  
21. 1722)  
22. 11 more




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

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

     首先自定义ImageView

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方法,用于监听图片视图的触摸事件

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

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. }

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



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

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

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中实现接口并设置

1. private class FloatViewListener implements FloatViewParamsListener {  
2. @Override  
3. public int getTitleHeight() {  
4. // 获取状态栏高度。不能在onCreate回调方法中获取  
5. 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时清理悬浮窗口

1. @Override  
2. public void onDestroy(){  
3. super.onDestroy();  
4. //  删除视图  
5.     mWindowManager.removeView(mImageView);  
6. }