FloatWindow 简单的悬浮窗

本文详细介绍了如何在Android中创建和适配一个简单的悬浮窗(FloatWindow),包括悬浮窗的滑动交互和双击放大的逻辑实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

悬浮窗 适配

完美的悬浮窗
支持滑动 双击放大逻辑

public class Test extends Activity {

    private static WindowManager mWindowManager;
    private WindowManager.LayoutParams wmParams;
    private int width, height;
    private FrameLayout mFloatLayout;

    private int screenWidth = ScreenUtils.getScreenWidth(this);
    private int screenHeight = ScreenUtils.getScreenHeight(this);
    private int localWidth = screenWidth / 4;
    private int localHeight = localWidth * 16 / 9;
    private boolean isBig = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initFloatWindow();
    }

    private void initFloatWindow() {

        wmParams = new WindowManager.LayoutParams();
        mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);
        // 获取屏幕的高度
        DisplayMetrics dm = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getMetrics(dm);
        width = dm.widthPixels;
        height = dm.heightPixels;

        // 设置window type
        if (Build.VERSION.SDK_INT >= 26) {  //  8.0新特性
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        }


        // 设置图片格式,效果为背景透明
        wmParams.format = PixelFormat.RGBA_8888;
        // 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams
                .FLAG_NOT_TOUCH_MODAL;

        // 调整悬浮窗显示的停靠位置为右侧侧置顶,方便实现触摸滑动
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;

        // 以屏幕左上角为原点,设置x、y初始值
        wmParams.x = width;
        wmParams.y = height * 2 / 3; // ScreenUtils.dp2px(this, 40);

        // 设置悬浮窗口长宽数据
        wmParams.width = localWidth;
        wmParams.height = localHeight;

        LayoutInflater inflater = LayoutInflater.from(getApplication());
        // 获取浮动窗口视图所在布局
        mFloatLayout = (FrameLayout) inflater.inflate(R.layout.float_window_layout, null);

        TextView vTime = new TextView(this);
        vTime.setText("ABCD");

        mFloatLayout.addView(vTime);

        initClick();
        setDoubleClick();
    }

    // 设置监听浮动窗口的触摸移动
    private void initClick() {
        mFloatLayout.setOnTouchListener(new View.OnTouchListener() {
            float dx, dy, mx, my;
            float moveX, moveY;
            // 这里用于up和move不冲突
            boolean isMove;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                // getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        // 记住手按下的位置
                        dx = event.getRawX();
                        dy = event.getRawY();
//                        mx = v.getWidth()/2;
//                        my = v.getHeight()/2;
                        // 计算手相对控件本身按下的位置
                        mx = event.getX();
                        my = event.getY();
                        isMove = false; // 这里需要设置默认值为false,避免up部分出bug
                        return false;
                    case MotionEvent.ACTION_MOVE:
                        // 计算手移动的距离
                        int x = Math.abs((int) (event.getRawX() - dx));
                        int y = Math.abs((int) (event.getRawY() - dy));
                        // 如果x和y距离都小于5,说明用户并没打算移动,只是手触摸时产生的move
                        if (x < 5 || y < 5) {
                            isMove = false;
                            return false;
                        } else {
                            isMove = true;
                        }
                        // 计算控件移动的距离
                        x = (int) (event.getRawX() - mx);
                        y = (int) (event.getRawY() - my);
                        wmParams.x = x;
                        // 25为状态栏的高度
                        wmParams.y = y;
                        // 刷新
                        mWindowManager.updateViewLayout(mFloatLayout, wmParams);
//                        return false;
                        return true;
                    case MotionEvent.ACTION_UP:
                        // 这里要注意边界问题
                        float finalX = event.getRawX();
                        float finalY = event.getRawY();
                        // 控制上边距
                        if (finalY < v.getHeight()) {
                            moveX = 0;
                            moveY = finalX - my;
                        }
                        // 下边距
                        if (finalY > height - v.getHeight()) {
                            moveX = finalX - mx;
                            moveY = finalY - v.getHeight();
                        }
                        // 判断控件改停留在左边距还是右边距
                        if (finalX - v.getWidth() / 2 < width / 2) {
                            moveX = 0;
                            moveY = finalY - my;
                        } else if (finalX - v.getWidth() / 2 > width / 2) {
                            moveX = width - v.getWidth();
                            moveY = finalY - my;
                        }
                        wmParams.x = (int) moveX;
                        wmParams.y = (int) moveY;
                        if (isMove) {
                            mWindowManager.updateViewLayout(mFloatLayout, wmParams);
                        }
//                        return isMove; // false为down,true为move
                        return false;
                    default:
                        break;
                }
                // 这里return ture,说明本次事件已经被处理,不会传给父亲
                return false;
            }
        });

    }

    private void setDoubleClick() {
        mFloatLayout.setOnClickListener(new DoubleClickListener() {
            @Override
            public void onMultiClick(View v) {
                // TODO: 2019/1/10   change state
                ToastUtil.toastNormal("change state");
                if (isBig) {
                    wmParams.width = localWidth;
                    wmParams.height = localHeight;
                    isBig = false;
                    mWindowManager.updateViewLayout(mFloatLayout, wmParams);
                } else {
                    isBig = true;
                    wmParams.width = screenWidth;
                    wmParams.height = screenHeight;
                    mWindowManager.updateViewLayout(mFloatLayout, wmParams);
                }
            }
        });
    }

}
public abstract class DoubleClickListener implements View.OnClickListener {

    // 两次点击按钮之间的点击间隔
    private static final int MIN_CLICK_DELAY_TIME = 500;
    private static long lastClickTime;

    public abstract void onMultiClick(View v);


    @Override
    public void onClick(View v) {
        long curClickTime = System.currentTimeMillis();
        if ((curClickTime - lastClickTime) <= MIN_CLICK_DELAY_TIME) {
            onMultiClick(v);
        } else {
            lastClickTime = curClickTime;
        }
    }
}

public class ScreenUtils {
    /**
     * 获取屏幕高度(px)
     */
    public static int getScreenHeight(Context context) {
        return context.getResources().getDisplayMetrics().heightPixels;
    }

    /**
     * 获取屏幕宽度(px)
     */
    public static int getScreenWidth(Context context) {
        return context.getResources().getDisplayMetrics().widthPixels;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值