Android悬浮窗及其拖动事件

主页面布局很简单,只有一个RelativelyLayout


<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


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

主界面代码

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);
    }
}

以上代码主要参考了这篇博客
【【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

你可能感兴趣的:(安卓学习)