比较简单,主要是使用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. }