zoukankan      html  css  js  c++  java
  • android 悬浮组件实现

    项目需求

    1. 需要实现一个每个页面都存在的悬浮按钮
    2. 可以拖动
    3. 跟随整个项目的生命周期(即应用登录之后显示悬浮按钮,应用退出之后,隐藏悬浮按钮)
    4. 特殊页面隐藏悬浮按钮
    5. 应用后台展示之后,隐藏悬浮按钮
    6. 应用恢复前台展示,显示悬浮按钮

    准备工作

    1. 添加权限
      1.  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    2. 悬浮窗口组件类 FloatWindowView,用于实现悬浮窗功能
    3. 悬浮窗管理类  FloatWindowManager,用于进行悬浮窗管理操作
    4. 悬浮窗服务  FloatWindowService,用于后台维护悬浮窗状态

    FloatWindowView.java

    public class FloatWindowView extends LinearLayout {
        // 系统状态栏的高度
        private static int                        statusBarHeight;
        // 用于更新小悬浮窗的位置
        private        WindowManager              windowManager;
        // 小悬浮窗的布局参数
        public         WindowManager.LayoutParams windowParams;
        // 记录当前手指位置在屏幕上的横坐标
        private        float                      xInScreen;
        // 记录当前手指位置在屏幕上的纵坐标
        private        float                      yInScreen;
        // 记录手指按下时在屏幕上的横坐标,用来判断单击事件
        private        float                      xDownInScreen;
        // 记录手指按下时在屏幕上的纵坐标,用来判断单击事件
        private        float                      yDownInScreen;
        // 记录手指按下时在小悬浮窗的View上的横坐标
        private        float                      xInView;
        // 记录手指按下时在小悬浮窗的View上的纵坐标
        private        float                      yInView;
        // 单击接口
        private        OnClickListener            listener;
    
        //表示悬浮窗的显示状态
        private boolean mHasShown;
        private long downTime = 0;
    
        /**
         * 构造函数
         *
         * @param context     上下文对象
         * @param layoutResId 布局资源id
         */
        public FloatWindowView(Context context, int layoutResId) {
            super(context);
            mHasShown = true;
            windowManager = (WindowManager) context
                    .getSystemService(Context.WINDOW_SERVICE);
    
            LayoutInflater.from(context).inflate(layoutResId, this);
            statusBarHeight = getStatusBarHeight();
            windowParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.TYPE_PHONE,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    PixelFormat.TRANSLUCENT
            );
            // 设置对齐方式为左上
            windowParams.gravity = Gravity.LEFT | Gravity.TOP;
            windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            windowParams.x = 0;
            windowParams.y = ScreenUtils.getScreenHeight();
        }
    
        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度
                case MotionEvent.ACTION_DOWN:
                    downTime = System.currentTimeMillis();
                    // 获取相对与小悬浮窗的坐标
                    xInView = event.getX();
                    yInView = event.getY();
                    // 按下时的坐标位置,只记录一次
                    xDownInScreen = event.getRawX();
                    yDownInScreen = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 时时的更新当前手指在屏幕上的位置
                    xInScreen = event.getRawX();
                    yInScreen = event.getRawY();
                    // 手指移动的时候更新小悬浮窗的位置
                    updateViewPosition();
                    break;
                case MotionEvent.ACTION_UP:
                    // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件
                    if (Math.abs(xDownInScreen - event.getRawX()) < 20
                            && Math.abs(yDownInScreen - event.getRawY()) < 20
                            && System.currentTimeMillis() - downTime < 1000) {
                        if (listener != null) {
                            listener.click();
                        }
                    }
                    break;
            }
            return true;
        }
    
        /**
         * 设置单击事件的回调接口
         */
        public void setOnClickListener(OnClickListener listener) {
            this.listener = listener;
        }
    
        /**
         * 更新小悬浮窗在屏幕中的位置
         */
        private void updateViewPosition() {
            windowParams.x = (int) (xInScreen - xInView);
            windowParams.y = (int) (yInScreen - yInView - statusBarHeight);
            windowManager.updateViewLayout(this, windowParams);
        }
    
        /**
         * 获取状态栏的高度
         *
         * @return
         */
        private int getStatusBarHeight() {
            try {
                Class<?> c     = Class.forName("com.android.internal.R$dimen");
                Object   o     = c.newInstance();
                Field    field = c.getField("status_bar_height");
                int      x     = (Integer) field.get(o);
                return getResources().getDimensionPixelSize(x);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        //悬浮窗的隐藏
        public void hide() {
            if (mHasShown) {
                windowManager.removeViewImmediate(this);
            }
            mHasShown = false;
        }
    
        //悬浮窗的显示
        public void show() {
            if (!mHasShown) {
                windowManager.addView(this, windowParams);
            }
            mHasShown = true;
        }
    
        /**
         * 单击接口
         *
         * @author zhaokaiqiang
         */
        public interface OnClickListener {
            public void click();
        }
    }
    

      FloatWindowManager.java

    public class FloatWindowManager {
        // 悬浮窗对象
        private        FloatWindowView    smallWindow;
        // 用于控制在屏幕上添加或移除悬浮窗
        private        WindowManager      mWindowManager;
        // FloatWindowManager的单例
        private static FloatWindowManager floatWindowManager;
        // 上下文对象
        private        Context            context;
    
        private FloatWindowManager(Context context) {
            this.context = context;
        }
    
        public static FloatWindowManager getInstance(Context context) {
            if (floatWindowManager == null) {
                floatWindowManager = new FloatWindowManager(context);
            }
            return floatWindowManager;
        }
    
        /**
         * 创建小悬浮窗
         *
         * @param context 必须为应用程序的Context.
         */
        public void createWindow(final Context context, int layoutResId
        ) {
            WindowManager windowManager = getWindowManager();
            if (smallWindow == null) {
                smallWindow = new FloatWindowView(context, layoutResId);
                smallWindow.setOnClickListener(new FloatWindowView.OnClickListener() {
                    long lastTime = 0;
    
                    @Override
                    public void click() {
                        if (System.currentTimeMillis() - lastTime > 1000) {
                            context.sendBroadcast(new Intent(SystemBroadcastReceiver.ACTION_FLOAT_CLICK));
                            lastTime = System.currentTimeMillis();
                        }
                    }
                });
                windowManager.addView(smallWindow, smallWindow.windowParams);
            }
        }
    
        /**
         * 将小悬浮窗从屏幕上移除
         */
        public void removeSmallWindow() {
            try {
                if (smallWindow != null) {
                    WindowManager windowManager = getWindowManager();
                    windowManager.removeView(smallWindow);
                    smallWindow = null;
                }
            } catch (Exception e) {
    
            }
    
        }
    
        //悬浮窗的隐藏
        public void hide() {
            if (smallWindow != null) {
                smallWindow.hide();
            }
        }
    
        //悬浮窗的显示
        public void show() {
            if (smallWindow != null) {
                smallWindow.show();
            }
        }
    
    
        public void removeAll() {
            context.stopService(new Intent(context, FloatWindowService.class));
            removeSmallWindow();
        }
    
        /**
         * 是否有悬浮窗显示(包括小悬浮窗和大悬浮)
         *
         * @return 有悬浮窗显示在桌面上返回true,没有的话返回false
         */
        public boolean isWindowShowing() {
            return smallWindow != null;
        }
    
        /**
         * 如果WindowManager还未创建,则创建新的WindowManager返回。否则返回当前已创建的WindowManager
         *
         * @return
         */
        private WindowManager getWindowManager() {
            if (mWindowManager == null) {
                mWindowManager = (WindowManager) context
                        .getSystemService(Context.WINDOW_SERVICE);
            }
            return mWindowManager;
        }
    }
    

     FloatWindowService.java

    public class FloatWindowService extends Service {
    
        public static final String  LAYOUT_RES_ID = "layoutResId";
        // 用于在线程中创建/移除/更新悬浮窗
        private             Handler handler       = new Handler();
        private             Context context       = this;
        private Timer timer;
        // 窗口布局资源id
        private int   layoutResId;
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            layoutResId = intent.getIntExtra(LAYOUT_RES_ID, 0);
            if (layoutResId == 0) {
                throw new IllegalArgumentException(
                        "layoutResId or rootLayoutId is illegal");
            }
            if (timer == null) {
                timer = new Timer();
                // 每500毫秒就执行一次刷新任务
                timer.scheduleAtFixedRate(new RefreshTask(), 0, 500);
            }
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            // Service被终止的同时也停止定时器继续运行
            FloatWindowManager.getInstance(context).removeAll();
            timer.cancel();
            timer = null;
        }
    
        private class RefreshTask extends TimerTask {
            @Override
            public void run() {
                // 当前界面没有悬浮窗显示,则创建悬浮
                if (!FloatWindowManager.getInstance(context).isWindowShowing()) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            FloatWindowManager.getInstance(context)
                                    .createWindow(context, layoutResId);
                        }
                    });
                } else {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            boolean isShowing = false;
                            //用于判断特殊页面是否展示悬浮窗,DlxApplication.activityClassList 为维护的一个活动的Activity列表
                            for (String clazz : DlxApplication.activityClassList) {
                                if (!clazz.equals(KnowledgeActivity.class.getCanonicalName()) && !clazz.equals(KnowledgeDetailActivity.class.getCanonicalName()) && isForeground(context, clazz)) {
                                    isShowing = true;
                                    break;
                                }
                            }
                            if (isShowing) {
                                show();
                            } else {
                                hide();
                            }
                        }
                    });
                }
            }
        }
    
        /**
         * 判断某个界面是否在前台
         *
         * @param context
         * @param className 某个界面名称
         */
        private boolean isForeground(Context context, String className) {
            if (context == null || StringUtil.isEmpty(className)) {
                return false;
            }
            ActivityManager                       am   = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningTaskInfo> list = am.getRunningTasks(1);
            if (list != null && list.size() > 0) {
                ComponentName cpn = list.get(0).topActivity;
                if (className.equals(cpn.getClassName())) {
                    return true;
                }
            }
            return false;
        }
    
        //悬浮窗的隐藏
        public void hide() {
            FloatWindowManager.getInstance(context).hide();
        }
    
        //悬浮窗的显示
        public void show() {
            FloatWindowManager.getInstance(context).show();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
    }            
    

      

    启动、结束悬浮窗

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    showFloat();
    }
    @Override
    protected void onDestroy() {
    super.onDestroy();
    removeFloat();
    }

    /** * 显示窗口 */ public void showFloat() {
          //判断SDK版本,高于等于 23版本的需要
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                if(!Settings.canDrawOverlays(self)){
                   //没有获取到悬浮窗权限,无法使用悬浮窗
                    return;
                }
            }
            // 需要传递小悬浮窗布局,以及根布局的id,启动后台服务
            Intent intent = new Intent(self, FloatWindowService.class);
            intent.putExtra(FloatWindowService.LAYOUT_RES_ID,
                    R.layout.layout_float
            );
            startService(intent);
        }
    
        /**
         * 移除所有的悬浮窗
         */
        public void removeFloat() {
         //判断SDK版本,高于等于 23版本的需要 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ if(!Settings.canDrawOverlays(self)){ return; } } Intent intent = new Intent(self, FloatWindowService.class); stopService(intent); }

      额外篇

      高于等于23版本的检测悬浮窗权限获取

    public void checkPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(self)) {
                    DialogHelper.showMessageDialog(self, "提示", "本应用需要授权悬浮窗权限,是否前往授权?", new OnItemClickMessageListener() {
                        @Override
                        public void onItemClickMessageListener(Message msg) {
                            Dialog dialog = (Dialog) msg.obj;
                            dialog.cancel();
                            if (msg.what == DialogHelper.TYPE_SUBMIT) {
                                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                        Uri.parse("package:" + getPackageName())
                                );
                                startActivityForResult(intent, MY_PERMISSIONS_REQUEST_FLOAT);
                            }
                        }
                    });
    
                }
            }
        }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == MY_PERMISSIONS_REQUEST_FLOAT) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(self)) {
    //没有获取到悬浮窗权限
    }
    }
    }
    }

      

     

  • 相关阅读:
    Node 文件上传,ZIP
    jquery实现前台倒计时。应用下单24小时后自动取消该订单
    solr 4.4添加索引是新手容易遇到的问题
    solr 4.6的安装配置
    java.lang.OutOfMemoryError: PermGen space
    java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO)
    mybatis代码生成(generator工具生成代码)
    再次熟悉jdbc连接mysql
    魔方阵,奇数阵。输入一个奇数,产生一个魔方阵
    错误,这个如何解决呢?内存溢出的问提。把JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m 还是不行
  • 原文地址:https://www.cnblogs.com/ProMonkey/p/6674444.html
Copyright © 2011-2022 走看看