Android 实现全局浮动弹窗

全局浮动小窗口,类似于微信的语音通话全局小窗口,还有一些手机的桌面的全局操作按钮等,算是比较常用,简单记录一下。

Android 实现全局浮动弹窗_第1张图片

实现原理:在Application中,getSystemService(Context.WINDOW_SERVICE) 获取WindowManager,然后通过WindowManager添加View, 手势滑动时实时更新该window的LayoutParamsx | y 坐标。

核心代码:

Application中创建window:

public class App extends Application {
     
    private SmallWindowView mWindowView;
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mLayoutParams;
    public SmallWindowView getWindowView() {
     
        return mWindowView;
    }
    public WindowManager getWindowManager() {
     
        return mWindowManager;
    }
    public WindowManager.LayoutParams getLayoutParams() {
     
        return mLayoutParams;
    }

    @Override
    public void onCreate() {
     
        super.onCreate();
        initSmallViewLayout();
    }

    public void initSmallViewLayout() {
     
        mWindowView = (SmallWindowView) LayoutInflater.from(this).inflate(R.layout.small_window, null);
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        mLayoutParams = new WindowManager.LayoutParams(
                getResources().getDimensionPixelSize(R.dimen.small_window_size), // 120dp
                getResources().getDimensionPixelSize(R.dimen.small_window_size),
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
        mLayoutParams.gravity = Gravity.NO_GRAVITY;
        mWindowView.setWm(mWindowManager);
        mWindowView.setWmParams(mLayoutParams);
    }

    public void showWindowView() {
     
        if (mWindowManager != null && mWindowView.getWindowId() == null) {
     
            mWindowManager.addView(mWindowView, mLayoutParams);
        }
    }

    public void dismissWindowView() {
     
        if (mWindowManager != null && mWindowView != null && mWindowView.getWindowId() != null) {
     
            mWindowManager.removeView(mWindowView);
        }
    }
} 

定义一个View组件,内部处理滑动改变窗口位置参数:

public class SmallWindowView extends LinearLayout {
     
    private final static String TAG = SmallWindowView.class.getSimpleName();
    private final int screenHeight;
    private final int screenWidth;
    private int statusHeight;
    private float mTouchStartX;
    private float mTouchStartY;

    private float mLastRawX;
    private float mLastRawY;

    private WindowManager wm;
    public WindowManager.LayoutParams wmParams;

    public WindowManager getWm() {
     
        return wm;
    }

    public void setWm(WindowManager wm) {
     
        this.wm = wm;
    }

    public WindowManager.LayoutParams getWmParams() {
     
        return wmParams;
    }

    public void setWmParams(WindowManager.LayoutParams wmParams) {
     
        this.wmParams = wmParams;
        this.wmParams.x = 0;//screenWidth/2; // 窗口先贴附在右边
    }

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

    public SmallWindowView(Context context, AttributeSet attrs) {
     
        this(context, attrs, 0);
    }

    public SmallWindowView(final Context context, AttributeSet attrs, int defStyle) {
     
        super(context, attrs, defStyle);
        statusHeight = getStatusHeight(context);
        DisplayMetrics dm = getResources().getDisplayMetrics();
        screenHeight = dm.heightPixels;
        screenWidth = dm.widthPixels;
        addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
     
            @Override
            public void onViewAttachedToWindow(View v) {
     
                //窗口内部按钮响应点击跳转打开新页面
                findViewById(R.id.btn).setOnClickListener(new OnClickListener() {
     
                    @Override
                    public void onClick(View v) {
     
                        Intent intent = new Intent(context, SecondActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(intent);
                    }
                });
                removeOnAttachStateChangeListener(this);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
     
            }
        });
    }

    /**
     * 获得状态栏的高度
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context) {
     
        int statusHeight = -1;
        try {
     
            Class clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
     
            e.printStackTrace();
        }
        return statusHeight;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
     
        switch (event.getAction()) {
     
            case MotionEvent.ACTION_DOWN:
                // 触摸点在View内的相对x坐标 相对Y坐标
                //mTouchStartX = event.getX();
                //mTouchStartY = event.getY();

                // 触摸点相对屏幕的x坐标 y坐标
                mLastRawX = event.getRawX();
                mLastRawY = event.getRawY() - statusHeight;
                Log.e(TAG, "startX = " + mLastRawX + " startY = " + mLastRawY);
                break;
            case MotionEvent.ACTION_MOVE:
                updateViewPosition(event.getRawX(), event.getRawY());
                mLastRawX = event.getRawX();
                mLastRawY = event.getRawY();
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return true;
    }

    /** 更新浮动窗口位置参数 */
    private void updateViewPosition(float x, float y) {
     
        wmParams.gravity = Gravity.NO_GRAVITY;
        //计算移动距离
        int dx = (int) (x - mLastRawX);
        int dy = (int) (y - mLastRawY);
        Log.e(TAG, "updateViewPosition: dx = " + dx + " dy = " + dy);
        //默认是以屏幕中心点为(0,0)起始坐标
        wmParams.x += dx;
        wmParams.y += dy;
        Log.e(TAG, "updateViewPosition: wmParams.x = " + wmParams.x + " wmParams.y = " + wmParams.y);
        wm.updateViewLayout(this, wmParams);
    }
}

定义一个基类Activity使用,并处理权限申请:

public class BaseActivity extends AppCompatActivity {
     
    private WindowManager wm;
    private SmallWindowView windowView;
    private WindowManager.LayoutParams mLayoutParams;
    private int OVERLAY_PERMISSION_REQ_CODE = 2;
    private boolean isRange = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        wm = ((App)getApplication()).getWindowManager();
        windowView = ((App)getApplication()).getWindowView();
        mLayoutParams = ((App)getApplication()).getLayoutParams();
    }


    public void alertWindow() {
     
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      // 7.0 以上需要引导用去设置开启窗口浮动权限
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      // 8.0 以上type需要设置成这个
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            }
            requestDrawOverLays();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      // 6.0 动态申请
            ActivityCompat.requestPermissions(this, new String[]{
     Manifest.permission.SYSTEM_ALERT_WINDOW}, 1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
     
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
     
            if (wm != null && windowView.getWm() == null) {
     
                wm.addView(windowView, mLayoutParams);
            }
        } else {
     
            Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
        }
    }


    private int[] location = new int[2]; // 小窗口位置坐标

    @Override
    public boolean onTouchEvent(MotionEvent event) {
     
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
     
            isRange = calcPointRange(event);
        }
        if (isRange) {
     
            windowView.dispatchTouchEvent(event);
        }
        return super.onTouchEvent(event);
    }

    /**
     *  计算当前点击事件坐标是否在小窗口内
     * @param event
     * @return
     */
    private boolean calcPointRange(MotionEvent event) {
     
        windowView.getLocationOnScreen(location);
        int width = windowView.getMeasuredWidth();
        int height = windowView.getMeasuredHeight();
        float curX = event.getRawX();
        float curY = event.getRawY();
        if (curX >= location[0] && curX <= location[0] + width && curY >= location[1] && curY <= location[1] + height) {
     
            return true;
        }
        return false;
    }

    private static final String TAG = "BaseActivity";

    // android 23 以上先引导用户开启这个权限 该权限动态申请不了
    @TargetApi(Build.VERSION_CODES.M)
    public void requestDrawOverLays() {
     
        if (!Settings.canDrawOverlays(BaseActivity.this)) {
     
            Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + BaseActivity.this.getPackageName()));
            startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
        } else {
     
            ((App) getApplication()).showWindowView();
            Toast.makeText(this, "权限已经授予", Toast.LENGTH_SHORT).show();
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     
        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
     
            if (!Settings.canDrawOverlays(this)) {
     
                Toast.makeText(this, "设置权限拒绝", Toast.LENGTH_SHORT).show();
            } else {
     
                Toast.makeText(this, "设置权限成功", Toast.LENGTH_SHORT).show();
            }
        }
    }
    // 移除window
    public void dismissWindow() {
     
        ((App) getApplication()).dismissWindowView();
    }
} 

全局系统弹窗需要申请权限,manfiest中一定要配置SYSTEM_ALERT_WINDOW权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

关键是这个权限不能通过动态权限申请的api去申请只能引导用户到设置页面手动开启,Settings.canDrawOverlays() 检测是否能在顶层绘制窗口,
new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())) 打开设置页面,开启悬浮窗口权限

Android 实现全局浮动弹窗_第2张图片

这个必须用户手动开启,是能开启悬浮窗口的前提。

参考:https://blog.csdn.net/luweicheng24/article/details/82053070

你可能感兴趣的:(Android,android,全局悬浮窗)