Android悬浮窗及其拖动事件

来源:互联网 发布:重复读单词软件 编辑:程序博客网 时间:2024/05/29 15:11

感谢原作者:尧石

主页面布局很简单,只有一个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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

悬浮窗中只有一个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);                        //如果原始位置在中间,所以需要减去屏幕宽高的一半//                      floatingButtonParams.x = (int) (lastX - mTouchStartX - contentWidth / 2);//                      floatingButtonParams.y = (int) (lastY - mTouchStartY - contentHeight / 2);                        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;        //下面这两个方法已经不建议使用了//      windowManager.getDefaultDisplay().getWidth();//      windowManager.getDefaultDisplay().getHeight();        Rect rect = new Rect();        // /取得整个视图部分,注意,如果你要设置标题样式,这个必须出现在标题样式之后,否则会出错        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);        top = rect.top;//状态栏的高度,所以rect.height,rect.width分别是系统的高度的宽度        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