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

Posted houruoyu3

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 实现无需权限的悬浮球效果,可适配至Android 10相关的知识,希望对你有一定的参考价值。

前言:

最近闲来无事,突然想起搞一下悬浮球,之前的项目的悬浮球一直都需要授权,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()以上是关于Android 实现无需权限的悬浮球效果,可适配至Android 10的主要内容,如果未能解决你的问题,请参考以下文章

Android 应用内悬浮控件实践总结

安卓悬浮球源代码(长按判断多次点击判断自动贴边)

Android 悬浮窗权限各机型各系统适配大全

Android 悬浮窗各机型各系统适配大全

android悬浮球实现各种功能快速开发框架单词笔记本应用市场应用等源码

Android无需权限显示悬浮窗