Android移动悬浮窗

一,WindowManager

1,想要实现悬浮窗,只需要几行代码就能搞定。这段代码就是把一个按钮添加到屏幕坐标为(100,100)的位置。

public class MainActivity extends AppCompatActivity {
    private Button mButton;
    private WindowManager.LayoutParams mLayoutParams;
    private WindowManager mWindowManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWindowManager = getWindowManager();
        mButton = new Button(this);
        mButton.setText("button");
        mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        mLayoutParams.x = 100;
        mLayoutParams.y = 100;
        mWindowManager.addView(mButton, mLayoutParams);

    }
}

WindowManager.LayoutParams中的flags和type参数比较重要

Flags参数表示Window的属性分别为:

  • FLAG_NOT_FOCUSABLE:Window不需要获取焦点,也不需要接受各种输入事件,此标记会同事启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的有焦点的Window。
  • FLAG_NOT_TOUCH_MODAL:系统会把当前Window区域以外的单击事件传递给底层Window,Window区域内的单击事件自己处理。
  • FLAG_SHOW_WHEN_LOCKED:Window可以显示在锁屏界面上。

Type表示Window的类型分别为:

(Window是分层级的,每个Window都有对应的z-ordered,层级大的会在最上层。)

  • 应用Window:应用类Window对应着一个Activity。层级范围 1~99
  • 子Window:子Window不能单独存在,它需要依附在父Window之中,比如Dialog就是一个子Window。层级范围 1000~1999
  • 系统Window:系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏。层级范围 2000~2999

2,WindowManager常用的有三个方法,addView(),updateViewLayout(),removeView()。
如果需要显示的View,随着滑动跟随一起移动这就用到了updateViewLayout()。我们可以给mButton 添加setOnTouchListener(this)

 @Override
    public boolean onTouch(View v, MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mLayoutParams.x = rawX;
                mLayoutParams.y = rawY;
                mWindowManager.updateViewLayout(mButton, mLayoutParams);
                break;
        }
        return false;
    }

二,悬浮框的实现

package com.efrobot.launcher.newlauncher.widget;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;




public class FloatWindow {
    private volatile static FloatWindow mInstance;
    private WindowManager.LayoutParams mLayoutParams;
    private DisplayMetrics mDisplayMetrics;
    private WindowManager mWindowManager;
    private Context mContext;
    private View mContentView;
    private static final int WHAT_HIDE = 0x275;
    private static final float DISTANCE = 15.0f;
    private float offsetX, offsetY;
    private long lastTouchTimeMillis;
    private long downTimeMillis;
    private boolean mIsShowing;
    private float downX, downY;
    private float oldX, oldY;
    private boolean isOpen;
    private View mFloatView, mPlayerView;

    public FloatWindow(Context context) {
        this(context, null, null);
    }

    public static FloatWindow getInstance(Context context) {
        if (mInstance == null) {
            synchronized (FloatWindow.class) {
                if (mInstance == null) {
                    mInstance = new FloatWindow(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 传入要显示的View
     *
     * @param context
     * @param contentView 点击悬浮框显示的内容
     * @param floatView   悬浮框
     */
    public FloatWindow(Context context, View contentView, View floatView) {
        this.mContext = context;
        setFloatView(floatView);
        setOpenView(contentView);
        initWindowManager();
        initLayoutParams();
    }

    /**
     * 传入悬浮框View
     *
     * @param floatView
     */
    public void setFloatView(View floatView) {
        if (floatView != null) {
            this.mFloatView = floatView;
            if (isShowing()) {
                getWindowManager().removeView(mContentView);
                createContentView(floatView);
                getWindowManager().addView(mContentView, getLayoutParams());
                updateLocation(getDisplayMetrics().widthPixels / 2, getDisplayMetrics().heightPixels / 2, true);
            } else {
                createContentView(floatView);
            }
        }
    }

    /**
     * 传入内容View
     *
     * @param contentView
     */
    public void setOpenView(View contentView) {
        if (contentView != null) {
            BackgroundView backgroundView = new BackgroundView(getContext());
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            layoutParams.topMargin = 40;
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP | RelativeLayout.CENTER_HORIZONTAL);
            contentView.setOnTouchListener(new TouchIntercept());
            contentView.setLayoutParams(layoutParams);
            backgroundView.addView(contentView);
            this.mPlayerView = backgroundView;
        }
    }

    /**
     * 设置窗口当前布局
     */
    private void setContentView(View floatView) {
        if (floatView != null) {
            if (isShowing()) {
                getWindowManager().removeView(mContentView);
                createContentView(floatView);
                getWindowManager().addView(mContentView, getLayoutParams());
                updateLocation(getDisplayMetrics().widthPixels / 2, getDisplayMetrics().heightPixels / 2, true);
            } else {
                createContentView(floatView);
            }
        }
    }


    private void createContentView(View contentView) {
        this.mContentView = contentView;
        contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); // 主动计算视图View的宽高信息
        offsetY = getStatusBarHeight(getContext()) + contentView.getMeasuredHeight() / 2;
        offsetX = contentView.getMeasuredWidth() / 2;
        contentView.setOnTouchListener(new WindowTouchListener());
    }

    /**
     * 获得上下文信息
     */
    public Context getContext() {
        return this.mContext;
    }

    public WindowManager getWindowManager() {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

    /**
     * 获得WindowManager.LayoutParams参数
     */
    public WindowManager.LayoutParams getLayoutParams() {
        if (mLayoutParams == null) {
            mLayoutParams = new WindowManager.LayoutParams();
            initLayoutParams();
        }
        return mLayoutParams;
    }

    /**
     * 获得显示信息
     */
    public DisplayMetrics getDisplayMetrics() {
        if (mDisplayMetrics == null) {
            mDisplayMetrics = getContext().getResources().getDisplayMetrics();
        }
        return mDisplayMetrics;
    }

    /**
     * 获得当前视图
     */
    public View getContentView() {
        return mContentView;
    }

    /**
     * 初始化窗口管理器
     */
    private void initWindowManager() {
        mWindowManager = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        mDisplayMetrics = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getMetrics(mDisplayMetrics);
    }

    /**
     * 初始化WindowManager.LayoutParams参数
     */
    private void initLayoutParams() {
        getLayoutParams().flags = getLayoutParams().flags
                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        getLayoutParams().dimAmount = 0.2f;
        getLayoutParams().type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        getLayoutParams().height = WindowManager.LayoutParams.WRAP_CONTENT;
        getLayoutParams().width = WindowManager.LayoutParams.WRAP_CONTENT;
        getLayoutParams().gravity = Gravity.LEFT | Gravity.TOP;
        getLayoutParams().format = PixelFormat.RGBA_8888;
        getLayoutParams().alpha = 1.0f;  //  设置整个窗口的透明度
        offsetX = 0;
        offsetY = getStatusBarHeight(getContext());
        getLayoutParams().x = (int) (mDisplayMetrics.widthPixels - offsetX);
        getLayoutParams().y = (int) (mDisplayMetrics.heightPixels * 1 / 4 - offsetY);
    }

    /**
     * 获取状态栏的高度
     */
    public int getStatusBarHeight(Context context) {
        int height = 0;
        int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resId > 0) {
            height = context.getResources().getDimensionPixelSize(resId);
        }
        return height;
    }

    /**
     * 更新窗口的位置
     */
    private void updateLocation(float x, float y, boolean offset) {
        if (getContentView() != null) {
            if (offset) {
                getLayoutParams().x = (int) (x - offsetX);
                getLayoutParams().y = (int) (y - offsetY);
            } else {
                getLayoutParams().x = (int) x;
                getLayoutParams().y = (int) y;
            }
            getWindowManager().updateViewLayout(mContentView, getLayoutParams());
        }
    }

    /**
     * 显示窗口
     */
    public void show() {
        if (getContentView() != null && !isShowing()) {
            getWindowManager().addView(getContentView(), getLayoutParams());
            mIsShowing = true;
            if (!isOpen) {
                handler.sendEmptyMessage(WHAT_HIDE);
            }
        }
    }

    /**
     * 隐藏当前显示窗口
     */
    public void dismiss() {
        if (getContentView() != null && isShowing()) {
            handler.removeMessages(WHAT_HIDE);
            getWindowManager().removeView(getContentView());
            mIsShowing = false;
        }
    }

    /**
     * 判断当前是否有显示窗口
     */
    public boolean isShowing() {
        return mIsShowing;
    }


    private ValueAnimator alignAnimator(float x, float y) {
        ValueAnimator animator = null;
        if (x <= getDisplayMetrics().widthPixels / 2) {
            animator = ValueAnimator.ofObject(new PointEvaluator(), new Point((int) x, (int) y), new Point(0, (int) y));
        } else {
            animator = ValueAnimator.ofObject(new PointEvaluator(), new Point((int) x, (int) y), new Point(getDisplayMetrics().widthPixels, (int) y));
        }
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point point = (Point) animation.getAnimatedValue();
                updateLocation(point.x, point.y, true);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                lastTouchTimeMillis = System.currentTimeMillis();
                handler.sendEmptyMessage(WHAT_HIDE);
            }
        });
        animator.setDuration(160);
        return animator;
    }

    /**
     *
     * */
    public class PointEvaluator implements TypeEvaluator {

        @Override
        public Object evaluate(float fraction, Object from, Object to) {
            Point startPoint = (Point) from;
            Point endPoint = (Point) to;
            float x = startPoint.x + fraction * (endPoint.x - startPoint.x);
            float y = startPoint.y + fraction * (endPoint.y - startPoint.y);
            Point point = new Point((int) x, (int) y);
            return point;
        }
    }

    /**
     * 打开选项菜单
     */
    public void showPlayer() {
        if (isOpen) {
            return;
        }
        getLayoutParams().height = WindowManager.LayoutParams.MATCH_PARENT;
        getLayoutParams().width = WindowManager.LayoutParams.MATCH_PARENT;
        oldX = getLayoutParams().x;
        oldY = getLayoutParams().y;
        Log.i("FloatWindow", "showPlayer: oldX=" + oldX + ",oldY=" + oldY);
        setContentView(mPlayerView);
        handler.removeMessages(WHAT_HIDE);
        isOpen = true;
    }

    /**
     * 关闭选项菜单
     */
    public void turnMini() {
        if (!isOpen) {
            return;
        }
        isOpen = false;
        getLayoutParams().flags &= ~(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);// 取消WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性
        getLayoutParams().flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;//重新设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性
        getLayoutParams().height = WindowManager.LayoutParams.WRAP_CONTENT;
        getLayoutParams().width = WindowManager.LayoutParams.WRAP_CONTENT;
        setContentView(mFloatView);
        getLayoutParams().alpha = 1.0f;
        updateLocation(oldX, oldY, false);
        lastTouchTimeMillis = System.currentTimeMillis();
        handler.sendEmptyMessage(WHAT_HIDE);
    }

    /**
     * 带有按键监听事件和触摸事件的BackgroundView
     */
    class BackgroundView extends RelativeLayout {

        public BackgroundView(Context context) {
            super(context);
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                if (getKeyDispatcherState() == null) {
                    return super.dispatchKeyEvent(event);
                }

                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null) {
                        state.startTracking(event, this);
                    }
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null && state.isTracking(event) && !event.isCanceled()) {
                        turnMini();
                        return true;
                    }
                }
                return super.dispatchKeyEvent(event);
            } else {
                return super.dispatchKeyEvent(event);
            }
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    turnMini();
                    return true;
                case MotionEvent.ACTION_OUTSIDE:
                    turnMini();
                    return true;
            }
            return super.onTouchEvent(event);
        }
    }

    class TouchIntercept implements View.OnTouchListener {

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    lastTouchTimeMillis = System.currentTimeMillis();
                    break;
            }
            return true;
        }
    }

    class WindowTouchListener implements View.OnTouchListener {

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (!isOpen) {
                        down(event);
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!isOpen) {
                        move(event);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (!isOpen) {
                        up(event);
                    }
                    break;
                case MotionEvent.ACTION_OUTSIDE:
                    if (isOpen) {
                        turnMini();
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return false;
        }
    }

    /**
     * 按下事件处理
     */
    private void down(MotionEvent event) {
        downX = event.getRawX();
        downY = event.getRawY();
        getLayoutParams().alpha = 1.0f;
        downTimeMillis = System.currentTimeMillis();
        lastTouchTimeMillis = System.currentTimeMillis();
        getWindowManager().updateViewLayout(getContentView(), getLayoutParams());
    }

    /**
     * 移动事件处理
     */
    private void move(MotionEvent event) {
        lastTouchTimeMillis = System.currentTimeMillis();
        updateLocation(event.getRawX(), event.getRawY(), true);
    }

    /**
     * 抬起事件处理
     */
    private void up(MotionEvent event) {
        float x = event.getRawX();
        float y = event.getRawY();
        if (x >= downX - DISTANCE && x <= downX + DISTANCE && y >= downY - DISTANCE && y <= downY + DISTANCE) {
            if (System.currentTimeMillis() - downTimeMillis > 1200) {
                //  长按
            } else {
                showPlayer();  //点击
            }
        } else {
            ValueAnimator animator = alignAnimator(x, y);
            animator.start();
        }
    }

    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WHAT_HIDE:
                    if (System.currentTimeMillis() - lastTouchTimeMillis >= 3500) {
                        if (!isOpen) {
                            getLayoutParams().alpha = 0.4f;
                            getWindowManager().updateViewLayout(getContentView(), getLayoutParams());
                        }
                    } else {
                        if (isOpen) {
                            lastTouchTimeMillis = System.currentTimeMillis() + 3500;
                        }
                        handler.sendEmptyMessageDelayed(WHAT_HIDE, 200);
                    }
                    break;
            }
        }
    };
}

调用起来就比较简单了,比如我们可以在service中这样写

  mFloatWindow =FloatWindow.getInstance(this);
  mFloatWindow.setFloatView(mFloatV);//传入悬浮框View
  mFloatWindow.setOpenView(mMenuV);//传入要打开的View
  mFloatWindow.show();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值