Android 实现无需权限的悬浮球效果,可适配至Android 10

2023-11-11

前言:

最近闲来无事,突然想起搞一下悬浮球,之前的项目的悬浮球一直都需要授权,android6.0以后需要手动授权,悬浮球使用时就非常不便,这里为大家带来一种无需权限的悬浮球实现方式。

  • 无需权限!
  • 无需权限!

功能:

  • 自动贴边
  • 显示红点
  • 隐藏红点
  • 自由移动
  • 显示悬浮球
  • 隐藏悬浮球
  • 销毁悬浮球
  • 接入简单,可进行自定义拓展

附上demo地址

效果如下:

在这里插入图片描述

以下是主要的代码部分:

MainActivity.class

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.showbtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RoundView.getInstance().showRoundView(MainActivity.this);

            }
        });

        findViewById(R.id.hidebtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RoundView.getInstance().hideRoundView(MainActivity.this);
            }
        });

        findViewById(R.id.closebtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RoundView.getInstance().closeRoundView(MainActivity.this);
            }
        });

        findViewById(R.id.showMsgbtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RoundView.getInstance().showRoundMsg();
            }
        });
        findViewById(R.id.hideMsgbtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                RoundView.getInstance().hideRoundMsg();
            }
        });
        findViewById(R.id.exitbtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                android.os.Process.killProcess(android.os.Process.myPid());
            }
        });
    }
}

RoundView.class

/**
 * 悬浮窗管理器
 */
public class RoundView {

    private static RoundView sFloatingLayer;

    /**
     * 用于控制在屏幕上添加或移除悬浮窗
     */
    private static WindowManager mWindowManager;

    public static boolean isNearLeft = true;  //判断悬浮球是否在左边

    private static boolean isShow = false;  //判断悬浮球是否已经显示

    private int mWidth,mHeight;   //屏幕的宽高

    public static final int WIN_NONE = 0;// 不展示
    public static final int WIN_SMALL = 1;// 小浮标
    public static final int WIN_BIG = 2;// 展开
    public static final int WIN_HIDE = 3;// 靠边隐藏 无操作隐藏
    public static int winStatus;

    public static boolean isMsg=false;  //红点消息提示是否显示


    /**
     * 小悬浮窗view的实例
     */
    private static RoundWindowSmallView smallWindow;


    /**
     * 隐藏悬浮窗view的实例
     */
    private static RoundWindowHideView hideWindow;

    /**
     * 大悬浮窗view的实例
     */
    private static RoundWindowBigView bigWindow;


    /**
     * 大小悬浮窗view的参数
     */
    private static WindowManager.LayoutParams mLayoutParams;


    public static RoundView getInstance() {
        if (null == sFloatingLayer) {
            synchronized (RoundView.class) {
                if (null == sFloatingLayer) {
                    sFloatingLayer = new RoundView();
                }
            }
        }
        return sFloatingLayer;
    }

    /**
     * 显示悬浮窗
     * @param context
     */
    public void showRoundView(Context context) {
        if(mWindowManager==null){
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }

        //每一次显示浮窗前都重新获取一次宽高,避免横竖屏切换后宽高变化
        getWidthAndHeight(context);

        if(!isShow){
            //处于非显示状态,可以显示
            isShow=true;
            if(winStatus==WIN_NONE){
                //处于未创建状态,请创建
                showSmallwin(context);
            }else {
                //已创建了,直接显示
                switch (winStatus){
                    case WIN_SMALL:
                        if(smallWindow!=null)
                            smallWindow.setVisibilityState(View.VISIBLE);
                        break;
                    case WIN_BIG:
                        if(bigWindow!=null)
                            bigWindow.setVisibilityState(View.VISIBLE);
                        break;
                    case WIN_HIDE:
                        if(hideWindow!=null)
                            hideWindow.setVisibilityState(View.VISIBLE);
                        break;
                }
            }
        }
    }

    /**
     * 隐藏悬浮窗
     * @param context
     */
    public void hideRoundView(Context context) {
        if(isShow){
            //处于显示状态,可以隐藏
            isShow=false;
            switch (winStatus){
                case WIN_SMALL:
                    if(smallWindow!=null)
                    smallWindow.setVisibilityState(View.GONE);
                    break;
                case WIN_BIG:
                    if(bigWindow!=null)
                    bigWindow.setVisibilityState(View.GONE);
                    break;
                case WIN_HIDE:
                    if(hideWindow!=null)
                    hideWindow.setVisibilityState(View.GONE);
                    break;
            }
        }
    }

    /**
     * 销毁悬浮窗
     * @param context
     */
    public void closeRoundView(Context context) {
        isShow=false;
        isMsg=false;
        winStatus=WIN_NONE;
        removeSmallWindow(context);
        removeBigWindow(context);
        removeHideWindow(context);
    }

    /**
     * 显示红点提示
     */
    public void showRoundMsg() {
        if(!isMsg){
            isMsg=true;
            switch (winStatus){
                case WIN_SMALL:
                    if(smallWindow!=null)
                        smallWindow.showRoundMsg();
                    break;
                case WIN_BIG:
                    if(bigWindow!=null)
                        bigWindow.showRoundMsg();
                    break;
                case WIN_HIDE:
                    if(hideWindow!=null)
                        hideWindow.showRoundMsg();
                    break;
            }
        }
    }

    /**
     * 隐藏红点提示
     */
    public void hideRoundMsg() {
        if(isMsg){
            isMsg=false;
            switch (winStatus){
                case WIN_SMALL:
                    if(smallWindow!=null)
                        smallWindow.hideRoundMsg();
                    break;
                case WIN_BIG:
                    if(bigWindow!=null)
                        bigWindow.hideRoundMsg();
                    break;
                case WIN_HIDE:
                    if(hideWindow!=null)
                        hideWindow.hideRoundMsg();
                    break;
            }
        }
    }

    /**
     * 显示小悬浮窗
     * @param context context
     */
    public void showSmallwin(final Context context) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                createSmallWindow(context);
                removeBigWindow(context);
                removeHideWindow(context);
            }
        }, 500);
    }


    /************************************************ 创建窗口 ************************************************************/

    /**
     * 创建一个小悬浮窗。初始位置在屏幕的左上角位置
     *
     * @param context 必须为应用程序的Context
     */
    public void createSmallWindow(Context context) {
        //每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
        getWidthAndHeight(context);
        if (smallWindow == null) {
            smallWindow = new RoundWindowSmallView(context);
            if (mLayoutParams == null) {
                mLayoutParams = new WindowManager.LayoutParams();

                mLayoutParams.format = PixelFormat.RGBA_8888;// 解决带Alpha的32位png图片失真问题
                mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; //显示在左上角
                mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                // 在设置宽高
                mLayoutParams.x = 0;
                mLayoutParams.y = context.getResources().getDisplayMetrics().heightPixels / 3 * 2;

                mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
                mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            }
        }

        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; //设置悬浮窗的层次

        if (!isNearLeft) {// 当在屏幕右侧时 重新计算x坐标
            int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            smallWindow.measure(w, h);
            if (Configuration.ORIENTATION_LANDSCAPE == context.getResources().getConfiguration().orientation) {// 横屏
                mLayoutParams.x = mWidth - smallWindow.getMeasuredWidth();
            } else {// 竖屏
                mLayoutParams.x = mWidth - smallWindow.getMeasuredWidth();
            }
        }

        smallWindow.setParams(mLayoutParams);

        // 将悬浮球添加到窗体
        if (smallWindow.getParent() == null) {
            mWindowManager.addView(smallWindow, mLayoutParams);
        }

        winStatus=WIN_SMALL;

        smallWindow.timehide();// 小悬浮窗3s后隐藏动画
    }

    /**
     * 将小悬浮窗从屏幕上移除
     *
     * @param context 必须为应用程序的context
     */
    public void removeSmallWindow(Context context) {
        if (smallWindow != null) {
            smallWindow.stopDelayed();
            if (context != null) {
                if (mWindowManager != null) {
                    try {
                        mWindowManager.removeView(smallWindow);
                    } catch (Exception e) {

                    }
                }
                smallWindow = null;
            } else {
                smallWindow = null;
            }
        }
    }

    /**
     * 创建一个隐藏悬浮窗。
     *
     * @param context 必须为应用程序的Context
     */
    public void createHideWindow(Context context) {

        //每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
        getWidthAndHeight(context);

        if (hideWindow == null) {

            int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            smallWindow.measure(w, h);// 测量

            int width = smallWindow.getMeasuredWidth();// 获得视图实际宽度(测量宽度)
            if (isNearLeft) {
                hideWindow = new RoundWindowHideView(context);
            } else {
                hideWindow = new RoundWindowHideView(context);
                mLayoutParams.x = mLayoutParams.x + width / 2;
            }

        }

        mWindowManager.addView(hideWindow, mLayoutParams);

        winStatus=WIN_HIDE;
    }


    /**
     * 将隐藏悬浮窗从屏幕上移除
     *
     * @param context 必须为应用程序的context
     */
    public void removeHideWindow(Context context) {
        if (hideWindow != null) {
            if (context != null) {
                if (mWindowManager != null) {
                    mWindowManager.removeView(hideWindow);
                }
            }
            hideWindow = null;
        }
    }

    /**
     * 创建一个大悬浮窗。
     *
     * @param context 必须为应用程序的Context
     */
    public  void createBigWindow(Context context) {

        //每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
        getWidthAndHeight(context);

        int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        if (bigWindow == null) {
            if (smallWindow != null) {
                smallWindow.measure(w, h);
            } else if (hideWindow != null) {
                hideWindow.measure(w, h);
            }
            bigWindow = new RoundWindowBigView(context);
        }
        bigWindow.measure(w,h);
        if (mLayoutParams.x>(mWidth-bigWindow.getMeasuredWidth())){
            // 在右边拖动的时候需要减去虚拟按钮
            mLayoutParams.x = mWidth-bigWindow.getMeasuredWidth();
        }
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL+1; //设置悬浮窗的层次 数据越大则越下面
        mWindowManager.addView(bigWindow, mLayoutParams);

        winStatus=WIN_BIG;
    }


    /**
     * 将大悬浮窗从屏幕上移除
     *
     * @param context 必须为应用程序的context
     */
    public void removeBigWindow(Context context) {
        if (bigWindow != null) {
            if (context != null) {
                if (mWindowManager != null) {
                    mWindowManager.removeView(bigWindow);
                }
            }
            bigWindow = null;
        }
    }



    /**
     * 获取全屏状态下的宽高
     * @param context
     */
    public void getWidthAndHeight(Context context){
        mWidth=context.getResources().getDisplayMetrics().widthPixels;
        mHeight = context.getResources().getDisplayMetrics().heightPixels;

        //有的手机是有虚拟导航键的,当横屏且全屏时,悬浮球无法靠到最右边,所以要用包含虚拟导航键的屏幕宽度
        Point point = new Point();
        if (mWindowManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                mWindowManager.getDefaultDisplay().getRealSize(point);
                mWidth=point.x;
                //mHeight =point.y;
            }
        }
    }
}

RoundWindowBigView.class

/**
 * 悬浮窗展开状态
 */
public class RoundWindowBigView extends LinearLayout {

    private Context context;
    private ImageView iv_content;

    /**
     * 红点消息提示
     */
    private View msg;


    public RoundWindowBigView(Context context) {
        super(context);
        this.context = context;
        if (RoundView.isNearLeft) {
            LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "pop_left"), this);
        } else {
            LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "pop_right"), this);
        }
        iv_content = (ImageView) findViewById(  FileUtil.getResIdFromFileName(context, "id", "iv_content"));

        msg=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg"));

        if(RoundView.isMsg){
            msg.setVisibility(VISIBLE);
        }else {
            msg.setVisibility(GONE);
        }

        setupViews();
    }

    private void setupViews() {
        iv_content.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        // 只能先创建在移除,不然有问题
                        RoundView.getInstance().createSmallWindow(context);
                        RoundView.getInstance().removeBigWindow(context);
                    }
                });
            }
        });

    }

    public void setVisibilityState(int state){
        this.setVisibility(state);
    }


    /**
     * 红点消息显示
     */
    public void showRoundMsg(){
        msg.setVisibility(VISIBLE);
    }

    /**
     * 红点消息隐藏
     */
    public void hideRoundMsg(){
        msg.setVisibility(GONE);
    }
}

RoundWindowHideView.class

/**
 * 悬浮窗贴边状态
 */
public class RoundWindowHideView extends LinearLayout {

    private ImageView hideview;
    private Context context;
    /**
     * 红点消息提示
     */
    private View msg;

    public RoundWindowHideView(Context context) {
        super(context);
        this.context = context;
        if (RoundView.isNearLeft) {
            LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "layout_hide_float_left"), this);
        } else {
            LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "layout_hide_float_right"), this);
        }
        hideview = (ImageView) findViewById(  FileUtil.getResIdFromFileName(context, "id", "hide_float_iv"));

        msg=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg"));

        if(RoundView.isMsg){
            msg.setVisibility(VISIBLE);
        }else {
            msg.setVisibility(GONE);
        }

        setupViews();
    }
    private void setupViews() {
        hideview.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                // 只能先创建在移除,不然有问题
                RoundView.getInstance().createSmallWindow(context);
                RoundView.getInstance().removeHideWindow(context);
            }
        });

    }

    public void setVisibilityState(int state){
        this.setVisibility(state);
    }

    /**
     * 红点消息显示
     */
    public void showRoundMsg(){
        msg.setVisibility(VISIBLE);
    }

    /**
     * 红点消息隐藏
     */
    public void hideRoundMsg(){
        msg.setVisibility(GONE);
    }

}

RoundWindowSmallView.class

/**
 * 悬浮窗常规状态
 */
public class RoundWindowSmallView extends LinearLayout {

    private static final String TAG = "RoundView";
    private Context context;

    private WindowManager windowManager;

    private WindowManager.LayoutParams mParams;

    private float mPrevX;
    private float mPrevY;
    private int mWidth,mHeight;

    private Timer mAnimationTimer;
    private AnimationTimerTask mAnimationTask;
    private Handler mHandler = new Handler();
    private int mAnimationPeriodTime = 16;

    private long mLastTouchDownTime;  //按下的时间
    private final int TOUCH_TIME_THRESHOLD = 150;  //少于150毫秒算点击事件
    private long lastClickTime;

    /**
     * 小悬浮窗对象
     */
    private View view;

    /**
     * 红点消息提示
     */
    private View msgLeft,msgRight;


    public RoundWindowSmallView(final Context context) {
        super(context);
        this.context=context;
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        mWidth=getContext().getResources().getDisplayMetrics().widthPixels;
        mHeight = getContext().getResources().getDisplayMetrics().heightPixels;

        Point point = new Point();
        if (windowManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                windowManager.getDefaultDisplay().getRealSize(point);
                mWidth=point.x;
                //mHeight =point.y;
            }
        }

        // 获取布局文件
        LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "round_view"), this);
        view = findViewById(FileUtil.getResIdFromFileName(context, "id", "rl_content"));
        msgLeft=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg_left"));
        msgRight=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg_right"));

        if(RoundView.isMsg){
            if(RoundView.isNearLeft){
                msgLeft.setVisibility(VISIBLE);
            }else {
                msgRight.setVisibility(VISIBLE);
            }
        }else {
            msgLeft.setVisibility(GONE);
            msgRight.setVisibility(GONE);
        }

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getActionMasked()) {
                    case MotionEvent.ACTION_DOWN:   //按下
                        mLastTouchDownTime = System.currentTimeMillis();
                        handler.removeMessages(1);
                        mPrevX = motionEvent.getRawX();
                        mPrevY = motionEvent.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float deltaX = motionEvent.getRawX() - mPrevX;
                        float deltaY = motionEvent.getRawY() - mPrevY;
                        mParams.x += deltaX;
                        mParams.y += deltaY;
                        mPrevX = motionEvent.getRawX();
                        mPrevY = motionEvent.getRawY();

                        //判断是否越界
                        if (mParams.x < 0) mParams.x = 0;
                        if (mParams.x > mWidth - view.getWidth())
                            mParams.x = mWidth - view.getWidth();
                        if (mParams.y < 0) mParams.y = 0;
                        if (mParams.y > mHeight - view.getHeight())
                            mParams.y = mHeight - view.getHeight();

                        try {
                            windowManager.updateViewLayout(view, mParams);
                        } catch (Exception e) {
                            Log.d(TAG, e.toString());
                        }

                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:  //松开
                        if (isOnClickEvent()) {
                            //添加点击事件
                            if (isFastDoubleClick()) {
                                //防止连续点击,如果连续点击这里什么也不做
                            } else {
                                //Toast.makeText(context, "你点击了悬浮球", Toast.LENGTH_SHORT).show();
                                new Handler().post(new Runnable() {
                                    @Override
                                    public void run() {
                                        RoundView.getInstance().createBigWindow(context);
                                        RoundView.getInstance().removeSmallWindow(context);
                                    }
                                });
                                return false;
                            }
                        }

                        //贴边
                        mAnimationTimer = new Timer();
                        mAnimationTask = new AnimationTimerTask();
                        mAnimationTimer.schedule(mAnimationTask, 0,mAnimationPeriodTime);
                        timehide();  //3s钟后隐藏
                        break;

                }

                return false;
            }
        });
    }


    /**
     * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置
     *
     * @param params 小悬浮窗的参数
     */
    public void setParams(WindowManager.LayoutParams params) {
        mParams = params;
    }


    //防止连续点击
    private boolean isFastDoubleClick() {
        long time = System.currentTimeMillis();
        if (time - lastClickTime < 500) {   //点击间隔设置大于动画执行时间,避免重复点击造成多次执行动画
            return true;
        }
        lastClickTime = time;
        return false;
    }
    protected boolean isOnClickEvent() {
        return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
    }

    class AnimationTimerTask extends TimerTask {

        int mStepX;
        int mDestX;

        public AnimationTimerTask() {
            if (mParams.x > mWidth / 2) {
                RoundView.isNearLeft = false;
                mDestX = mWidth - view.getWidth();
                mStepX = (mWidth - mParams.x) / 10;
            } else {
                RoundView.isNearLeft = true;
                mDestX = 0;
                mStepX = -((mParams.x) / 10);
            }
        }

        @Override
        public void run() {
            if (Math.abs(mDestX - mParams.x) <= Math.abs(mStepX)) {
                mParams.x = mDestX;
            } else {
                mParams.x += mStepX;
            }
            try {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        updateViewPosition();
                    }
                });
            } catch (Exception e) {
                Log.d(TAG, e.toString());
            }
            if (mParams.x == mDestX) {
                mAnimationTask.cancel();
                mAnimationTimer.cancel();
            }
        }


    }

    public void setVisibilityState(int state){
        if(state==View.VISIBLE){
            timehide();
        }else if(state==View.GONE){
            handler.removeMessages(1);
        }
        this.setVisibility(state);
    }

    public void updateViewPosition(){
        windowManager.updateViewLayout(this, mParams);
    }

    /**
     * 显示小浮标是否添加3秒隐藏
     */
    public void timehide() {
        Message msg = new Message();
        msg.what = 1;
        handler.sendMessageDelayed(msg, 3000);
    }

    /**
     * 小浮标停止隐藏
     */
    public void stopDelayed() {
        if (handler != null) {
            handler.removeMessages(1);
        }
    }
    /**
     * handler处理:隐藏半球
     */
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    hidePop();
                    break;
                default:
                    break;
            }
        }
    };

    /**
     * 退出动画
     *
     */
    public void hidePop(){
        Animation mAnimation = null;
        if (RoundView.isNearLeft) {
            mAnimation = AnimationUtils.loadAnimation(context,  FileUtil.getResIdFromFileName(context, "anim", "slide_out_left"));
        } else {
            mAnimation = AnimationUtils.loadAnimation(context,   FileUtil.getResIdFromFileName(context, "anim", "slide_out_right"));
        }
        view.startAnimation(mAnimation);
        mAnimation.setAnimationListener(new Animation.AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                //动画执行完毕后切换视图
                // 只能先创建在移除,不然有问题
                RoundView.getInstance().createHideWindow(context);
                RoundView.getInstance().removeSmallWindow(context);
            }
        });
    }

    /**
     * 红点消息显示
     */
    public void showRoundMsg(){
        if(RoundView.isNearLeft){
            msgLeft.setVisibility(VISIBLE);
        }else {
            msgRight.setVisibility(VISIBLE);
        }
    }

    /**
     * 红点消息隐藏
     */
    public void hideRoundMsg(){
        msgLeft.setVisibility(GONE);
        msgRight.setVisibility(GONE);
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 实现无需权限的悬浮球效果,可适配至Android 10 的相关文章

  • 使用 Guava 联合两个 ImmutableEnumSets

    我想联合两个ImmutableEnumSets来自番石榴 这是我的尝试 public final class OurColors public enum Colors RED GREEN BLUE YELLOW PINK BLACK pub
  • 在 Android 中使用 iText 读取或打开 PDF 文件

    我是 Android 应用程序开发新手 使用 iText 我完成了 PDF 创建并在创建的文件上写入 现在我想阅读该 PDF 文件 如何使用 iText 打开或阅读 PDF 文件 例子将是可观的 那么提前 哪个是渲染 PDF 文件的最佳库
  • 如何覆盖日期选择器的高度和宽度以填充父布局

    我有一个活动包含一个日期选择器 我想设置DatePicker适合屏幕 我试过这个答案 https stackoverflow com questions 6674667 how to customize date pickers width
  • 如何在java中使jpeg无损?

    有没有人可以告诉我如何使用编写 jpeg 文件losslessjava中的压缩 我使用下面的代码读取字节来编辑字节 WritableRaster raster image getRaster DataBufferByte buffer Da
  • 当应用程序未运行时如何堆叠 Firebase Cloud Messaging 通知?

    我在用Firebase Cloud Messaging将推送通知从我的服务器发送到我的 Android 应用程序 当应用程序运行时 通知是stacked因为我将它们设置为我的一个组FirebaseMessagingService 这很好 但
  • 模拟器:进程已完成,退出代码为 134(被信号 6:SIGABRT 中断)

    我最近刚刚开始在 Mac 上下载 Android Studio 版本 3 0 1 但收到以下错误 模拟器 进程已完成 退出代码为 134 被信号 6 SIGABRT 中断 我按照 Android Studio 教程操作并能够运行模拟器 但在
  • 在 AKKA 中,对主管调用 shutdown 是否会停止其监督的所有参与者?

    假设我有一位主管连接了 2 位演员 当我的应用程序关闭时 我想优雅地关闭这些参与者 调用supervisor shutdown 是否会停止所有参与者 还是我仍然需要手动停止我的参与者 gracias 阻止主管 https github co
  • Android开发:未定义方法

    大家好 我是 Android 和 Eclipse 的新手 我刚刚遵循了developer android com 上的教程 现在我在添加操作栏 http developer android com training basics actio
  • 如何在android中录制音频时暂停背景音乐

    我正在 Android 中开发一个音频记录应用程序 因此 如果设备音乐播放器中已播放任何背景音乐 则应在开始录制之前暂停该背景音乐 并且每当录制停止或暂停时 背景音乐都应恢复 播放录制的音频时也应该如此 有人可以帮我解决这个问题吗 提前致谢
  • 带有空白白屏的 WebView

    我在 DialogFragment 中有一个 webview 它使用以下方式显示文档和 PDF它可以进行几次尝试 但如果用户尝试频繁打开和关闭对话框 webview 将显示空白屏幕 我已经尝试了所有的线程link1 https stacko
  • Android:单一活动,多个视图

    我不是 Android 专业人士 尽管我开发了一个包含 50 多个活动的应用程序 这使得该应用程序非常庞大 经过8周的开发 现在出现了一些问题 导致应用程序难以维护和升级 我正在处理的主要问题是 我无法将对象引用传递给活动的构造函数 事实上
  • 来自客户端的超时 Web 服务调用

    我正在使用 RestEasy 客户端调用网络服务 一项要求是 如果调用运行时间超过 5 秒 则中止 超时调用 我如何使用 RestEasy 客户端实现这一目标 我只看到服务器端超时 即如果在一定时间内未完成请求 Rest Easy 网络服务
  • Google Android Drive api 在已安装版本上登录失败

    我开发了一个使用 GoogleDrive api 的 Android 应用程序 当处于调试状态或运行调试版本时 应用程序 工作正常 并正确验证附加的谷歌帐户 等 当我构建发行版本时 使用我的签名密钥 并且 安装apk文件 当我运行时 Goo
  • Spock模拟inputStream导致无限循环

    我有一个代码 gridFSFile inputStream bytes 当我尝试这样测试时 given def inputStream Mock InputStream def gridFSDBFile Mock GridFSDBFile
  • gradle-experimental:0.1.0 buildConfigField

    谁知道怎么定义buildConfigField在实验性的 gradle 插件中 android productFlavors create demo applicationId com anthonymandra rawdroid buil
  • 基于BluetoothChat示例通过蓝牙套接字发送文件

    大家好 根据我之前问的一个问题 我已经能够将文件转换为其他字节数组 以便使用以下写入方法 public void sendFile Log d TAG sending data InputStream inputStream null Ur
  • Android应用程序kill事件捕获

    我想在我的应用程序被终止时执行一些操作 可以使用哪种方法来实现此目的 我正在开发 Android 5 0 这个问题的关键在于 您必须了解您的申请是否可以收到任何 当您的应用程序在任何情况下被终止时的额外回调 下面的答案是由德文连线 http
  • Amazon IAP 不会调用 onPurchaseResponse

    我有一个 Android 应用程序 它使用 IAP 我正在发送PurchasingManager initiateGetUserIdRequest 并得到用户识别成功 in onGetUserIdResponse 得到回复后Purchasi
  • 修改 ADW Android 启动器?

    我想更改和修改开源 ADW 启动器 启动器可在此处获取 https github com AnderWeb android packages apps Launcher https github com AnderWeb android p
  • 尝试使用带有有效购买令牌的 Java Google Play Developer API v3 检索应用内购买信息时出现错误请求(无效值)

    当使用 Java Google Play Developer API 版本 3 并请求有效购买令牌的购买信息时 我收到以下异常 API 调用返回 400 Bad Request 响应以及以下消息 code 400 errors domain

随机推荐

  • Python+Neo4j构建时光网TOP100电影知识图谱

    Python Neo4j构建时光网TOP100电影知识图谱 环境 1 Neo4j 3 5 6 2019年6月25日 2 Java 1 8 0 181 3 Annaconda 3 一 准备工作 Neo4j安装 https blog csdn
  • 随机森林详解

    原文链接 机器学习之随机森林 RF 详解 文章目录 一 bagging算法 1 简介 2 bagging算法流程 二 随机森林 1 简介 2 CART分类树的生成 3 总结 常用集成学习包括Bagging Boosting Stacking
  • SpringCloud-Alibaba整合Nacos+Seata+Mybatis-Plus

    SpringCloud Alibaba整合Nacos Seata Mybatis Plus Seata Example 项目说明 准备工作 配置数据库 创建 undo log 表 创建 示例中 业务所需要的数据库表 启动 Seata Ser
  • 基于spring boot实现企业微信登录

    基于spring boot实现企业微信登录
  • JS关键字、保留字(日志)

    关键字 是指JS本身已经使用了的字 不能再用它们充当变量名 方法名 包括 break case catch continue default delete do else finally for function if in instanc
  • docker安装fastdfs集群

    在dockerhub上搜索fastdfs 我选择了使用最多的星星最多的镜像 season fastdfs 拉取镜像 docker pull season fastdfs 创建数据卷 docker volume create tracker
  • 恒指李阳12.9日预测及操作建议

    恒指早盘资讯 上周五美股集体收涨 道指收涨逾330点 标普500指数收涨0 91 科技股普涨 苹果涨近2 再创历史新高 港股ADR指数小幅下跌 按比例计算 收报26489点 跌9 13点或0 03 汇丰控股收报57 74元 较港收市升0 2
  • 避免同一个文件被include多次

    在C C 中 为了避免同一个文件被include多次 有两种方式 一种是 ifndef方式 一种是 pragma once方式 在头文件的最开始加入 ifndef ifndef的是方式是受C C 语言标准支持 ifndef方式依赖于宏名不能
  • TS 的类

    一 基础语法 class Person constructor 二 类的属性 1 属性的初始化 在 TS 中 我们如果在要 constructor 中定义一个属性 必须先在 constructor 之前对数据进行初始化 class Pers
  • css禁止滑动页面_弹出层完美禁止页面滚动

    Html Css Js 弹出层去除背景滚动 原生版 使用js构建的弹出层类 使用new的方式实例化 传入的参数为 el 触发元素 click触发弹出层 content 弹出层内容 time 0 显示持续时间 使用 passive 事件和ov
  • mysql一对多关联查询语句_mysql 一对多的时分 关联查询 筛选多方

    mysql 一对多的时候 关联查询 筛选多方 users 表和 auth token log表是一对多 现在是把user的信息找出来 关联上一些 auth token log表的数据 因为a表是多的一方 要多他的数据进行一些条件匹配 这个s
  • 数字化时代-7:从三大产业看赚钱的立足点

    摘要 通过观察不同产业的发展特点 发现社会发展的趋势 农业社会 工业社会 信息社会 虚拟社会演进 物质产品生产 精神产品的生产 把握大势 让猪遇到风飞起来 违背大势 飞机也会坠毁 何为产业 主要是指在经济社会中 能够生产某种类型产品的部门的
  • 出门问问把AIGC看得很透彻

    图片来源 Pixabay 李志飞将AIGC看得很透 他可能是行业里 最不着急 的人 数科星球原创 作者丨苑晶 编辑丨大兔 几乎每一个行业 从业者都是国外的2倍 利润又是同行的1 5 让中国的创业难度提升了10倍 李志飞的这句话适用于广大行业
  • Echarts 各种点击事件监听

    目录 一 鼠标事件 1 1 左击 1 2 双击 1 3 右击 1 4 右键双击 1 5 中轴滚动 二 时间轴 2 1 时间轴监听 三 拖动 3 1 拖动事件 一 鼠标事件 1 1 左击 chart on click function par
  • 乘积尾零(蓝桥杯省赛2018C/C++A组第三题)

    题目 如下的 10 行数据 每行有 10 个整数 请你求出它们的乘积的末尾有多少个零 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 6113 5659 5245 7432 3
  • 图像分类篇:实现pytorch官网demo(LeNet)

    跟着b站up主学习整理 2 1 pytorch官方demo Lenet 哔哩哔哩 bilibili 目录 一 CNN的雏形 LeNet网络结构 二 官网demo文件 三 代码实现 1 model py 2 train py 3 predic
  • 最酷炫的Python进度条使用教程

    原文链接 https mp weixin qq com s gRpNq15bX4OgpiY5NNO OQ 效果展示 基础使用 导入 alive progress 库 from alive progress import alive bar
  • overleaf入门教程

    overleaf入门教程 中文学习文档 供自己学习记录 1 编译中文 输入内容为中文时 有两种方式 需要将 documentclass article 改为 documentclass ctexart 或者引用宏包 usepaceage c
  • 获取每个年龄段的人数

  • Android 实现无需权限的悬浮球效果,可适配至Android 10

    前言 最近闲来无事 突然想起搞一下悬浮球 之前的项目的悬浮球一直都需要授权 android6 0以后需要手动授权 悬浮球使用时就非常不便 这里为大家带来一种无需权限的悬浮球实现方式 无需权限 无需权限 功能 自动贴边 显示红点 隐藏红点 自