感谢原作者:尧石
主页面布局很简单,只有一个RelativelyLayout
<?xml version="1.0" encoding="utf-8"?><RelativeLayout android:id="@+id/rl_content" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="example.floatingviewtest.MainActivity"></RelativeLayout>
悬浮窗中只有一个TextView
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="这是一个悬浮TextView" android:paddingTop="30dp" android:paddingBottom="30dp" android:background="@color/colorPrimary" android:textSize="15sp"/></LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
主界面代码
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; /** * 获取sdk版本号 */ private static final int SDKVERSION = Build.VERSION.SDK_INT; /** * 窗口管理器 */ private WindowManager windowManager; /** * 浮动按钮布局 */ private View floatingButtonView; /** * 浮动按钮布局参数 */ private WindowManager.LayoutParams floatingButtonParams; /** * 顶部状态栏高度 */ private int top; /** * 浮动窗原始位置 */ private float startPositionX = 0; private float startPositionY = 0; /** * 屏幕宽高 */ private int contentWidth; private int contentHeight; private float lastX; private float lastY; private float mTouchStartX; private float mTouchStartY; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); initFloatingButton(); } private void initFloatingButton() { floatingButtonView = LayoutInflater.from(this).inflate(R.layout.floating_view, null); floatingButtonParams = new WindowManager.LayoutParams(); if (SDKVERSION >= 19) { floatingButtonParams.type = WindowManager.LayoutParams.TYPE_TOAST; } else { floatingButtonParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } floatingButtonParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams .FLAG_NOT_FOCUSABLE; floatingButtonParams.format = PixelFormat.TRANSLUCENT; floatingButtonParams.width = WindowManager.LayoutParams.WRAP_CONTENT; floatingButtonParams.height = WindowManager.LayoutParams.WRAP_CONTENT; floatingButtonParams.gravity = Gravity.TOP| Gravity.LEFT; floatingButtonParams.x = 0; floatingButtonParams.y = 0; Log.d(TAG, "initFloatingButton: " + floatingButtonParams.x + " " + floatingButtonParams.y); windowManager.addView(floatingButtonView, floatingButtonParams); floatingButtonView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { lastX = event.getRawX(); lastY = event.getRawY() - top; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mTouchStartX = event.getX(); mTouchStartY = event.getY(); startPositionX = floatingButtonParams.x; startPositionY = floatingButtonParams.y; Log.d(TAG, "onTouch down : m " + mTouchStartX + " " + mTouchStartY); Log.d(TAG, "onTouch down : last " + lastX + " " + lastY); Log.d(TAG, "onTouch down : start " + startPositionX + " " + startPositionY); Log.d(TAG, "onTouch down : Params " + floatingButtonParams.x + " " + floatingButtonParams .y); break; case MotionEvent.ACTION_MOVE: floatingButtonParams.x = (int) (lastX - mTouchStartX); floatingButtonParams.y = (int) (lastY - mTouchStartY); Log.d(TAG, "onTouch move : m " + mTouchStartX + " " + mTouchStartY); Log.d(TAG, "onTouch move : last " + lastX + " " + lastY); Log.d(TAG, "onTouch move : start " + startPositionX + " " + startPositionY); Log.d(TAG, "onTouch move : Params " + floatingButtonParams.x + " " + floatingButtonParams .y); windowManager.updateViewLayout(floatingButtonView, floatingButtonParams); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onTouch up : m " + mTouchStartX + " " + mTouchStartY); Log.d(TAG, "onTouch up : last " + lastX + " " + lastY); Log.d(TAG, "onTouch up : start " + startPositionX + " " + startPositionY); Log.d(TAG, "onTouch up : Params " + floatingButtonParams.x + " " + floatingButtonParams.y); if (Math.abs(floatingButtonParams.x - startPositionX) < 20 && Math.abs(floatingButtonParams.y - startPositionY) < 20) { Toast.makeText(MainActivity.this, "click", Toast.LENGTH_LONG); } break; } return false; } }); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Point size = new Point(); windowManager.getDefaultDisplay().getSize(size); contentWidth = size.x; contentHeight = size.y; Rect rect = new Rect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); top = rect.top; Log.d(TAG, "onWindowFocusChanged: " + contentWidth + " " + contentHeight + " " + top); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
以上代码主要参考了这篇博客
【【Android Demo】悬浮窗体实现】http://www.cnblogs.com/yc-755909659/p/4281214.html。
这篇博客【android悬浮窗口的实现】http://blog.csdn.net/stevenhu_223/article/details/8504058也是关于悬浮窗的,里面有源码分析,讲的更深入一些,还没来得及学习。
但是上述代码有点问题,如果将悬浮窗的初始位置设为Gravity.CENTER,在拖动的最开始会有个抖动。如果将主题设为没有title的主题,然后出去界面的宽高,在拖动的时候减去宽高的一半,则没有抖动。可是加上有title的主题后,会有微小的抖动。这一bug暂未修复。
为什么悬浮窗的属性要设置成TYPE_TOAST,请看这里
1、使用 type 值为 WindowManager.LayoutParams.TYPE_PHONE 和 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 需要申请 android.permission.SYSTEM_ALERT_WINDOW 权限,否则无法显示,报错:
E/AndroidRuntime: android.view.WindowManagerBadTokenException:Unabletoaddwindowandroid.view.ViewRootW@b64b5458 – permission denied for this window type
2、type 值为 WindowManager.LayoutParams.TYPE_TOAST 显示的 System overlay view 不需要权限,即可在任何平台显示。
但仅在 API level >= 19 时可以达到目的。API level 19 以下因无法接收无法接收触摸(点击)和按键事件,故无法达到目的。
3、对于 API level < 19 的机器(MIUI除外),想要达到目的,需要:
要有 android.permission.SYSTEM_ALERT_WINDOW 权限
将 type 设置为 WindowManager.LayoutParams.TYPE_PHONE 或者 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
具体参考如下两篇博客
Android悬浮窗的小结:http://liaohuqiu.net/cn/posts/android-windows-manager/
Android悬浮窗TYPE_TOAST小结: 源码分析:http://www.jianshu.com/p/634cd056b90c