Android悬浮窗及其拖动事件

感谢原作者:尧石

主页面布局很简单,只有一个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.WindowManager BadTokenException:Unabletoaddwindowandroid.view.ViewRoot W@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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值