悬浮窗

作者: GordenNee | 来源:发表于2017-04-26 16:09 被阅读56次

    目标

    • 生成一个全局的悬浮窗,即使应用退出,悬浮窗也要保持存在,并可以保持其功能正常
    • 支持自由拖动
    • 动态更新内容(流量,网速等)
    • 支持无需权限申请(额外)

    步骤

    • 构建自定义View支持自定义视图,重写onTouch() 支持自由移动,支持点击事件处理
    • 构建Manager,处理悬浮窗的显示,更新,关闭等事件
    • 通过Manager来调用悬浮窗的加载

    实例

    1. 自定义View

    这里继承LinearLayout,当然可以根据需求来自定义。

    /**
     * 悬浮窗
     */
    public class FloatWindowView extends LinearLayout {
        // 小悬浮窗的宽
        public int viewWidth;
        // 小悬浮窗的高
        public int viewHeight;
        // 系统状态栏的高度
        private static int statusBarHeight;
        // 用于更新小悬浮窗的位置
        private WindowManager windowManager;
        // 小悬浮窗的布局参数
        public WindowManager.LayoutParams smallWindowParams;
        // 记录当前手指位置在屏幕上的横坐标
        private float xInScreen;
        // 记录当前手指位置在屏幕上的纵坐标
        private float yInScreen;
        // 记录手指按下时在屏幕上的横坐标,用来判断单击事件
        private float xDownInScreen;
        // 记录手指按下时在屏幕上的纵坐标,用来判断单击事件
        private float yDownInScreen;
        // 记录手指按下时在小悬浮窗的View上的横坐标
        private float xInView;
        // 记录手指按下时在小悬浮窗的View上的纵坐标
        private float yInView;
        // 单击接口
        private OnClickListener listener;
    
        /**
         * 构造函数
         * @param context
         * 上下文对象
         */
        public FloatWindowView(Context context) {
            super(context);
            windowManager = (WindowManager)          MainApplication.getContext().getSystemService(Context.WINDOW_SERVICE);
            LayoutInflater.from(MainApplication.getContext()).inflate(R.layout.layout_test, this);
            statusBarHeight = getStatusBarHeight();
            TextView textView = (TextView) findViewById(R.id.window_tv);
            textView.setText("悬浮窗");
    
            smallWindowParams = new WindowManager.LayoutParams();
            // 设置显示类型为phone
            smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            // 显示图片格式
            smallWindowParams.format = PixelFormat.RGBA_8888;
            // 设置交互模式
            smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            // 设置对齐方式为左上
            smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
            smallWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            smallWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            smallWindowParams.x = 0;
            smallWindowParams.y = 0;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            DebugLog.d("aaaa",event.getAction()+"");
            switch (event.getAction()) {
                // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度
                case MotionEvent.ACTION_DOWN:
                    // 获取相对与小悬浮窗的坐标
                    xInView = event.getX();
                    yInView = event.getY();
                    // 按下时的坐标位置,只记录一次
                    xDownInScreen = event.getRawX();
                    yDownInScreen = event.getRawY() - statusBarHeight;
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 时时的更新当前手指在屏幕上的位置
                    xInScreen = event.getRawX();
                    yInScreen = event.getRawY() - statusBarHeight;
                    // 手指移动的时候更新小悬浮窗的位置
                    updateViewPosition();
                    break;
                case MotionEvent.ACTION_UP:
                    // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件
                    if (xDownInScreen - event.getRawX() < 5
                            && yDownInScreen - (event.getRawY() - getStatusBarHeight()) <5) {
                        if (listener != null) {
                            listener.onClick(this);
                        }
                    }
                    break;
            }
            return true;
        }
    
        /**
         * 更新悬浮窗在屏幕中的位置
         */
        private void updateViewPosition() {
            smallWindowParams.x = (int) (xInScreen - xInView);
            smallWindowParams.y = (int) (yInScreen - yInView);
            windowManager.updateViewLayout(this, smallWindowParams);
        }
    
        /**
         * 设置单击事件的回调接口
         */
        public void setOnClickListener(OnClickListener listener) {
            this.listener = listener;
        }
    
        /**
         * 获取状态栏的高度
         * @return
         */
        private int getStatusBarHeight() {
            Rect frame = new Rect();
            getWindowVisibleDisplayFrame(frame);
            int statusBarHeight = frame.top;
            return statusBarHeight;
        }
    
    }
    

    Layout 是一个线性布局,包含了一个TextView和一个ImageView。同时预留了setOnclickLintener(),供外部传入具体的Click实现。

    2.构建Manager

    • 在Manager内部对自定义View进行 初始化
    • 调用WindowManager 进行addView 添加到window中显示
    • 传入一个ClickListener对视图进行点击监听
    • 提供关闭悬浮窗方法。
    /**
     * 悬浮窗管理器
     *
     * @author zhaokaiqiang
     *
     */
    public class FloatWindowManager {
    
        private FloatWindowView floatWindowView;
    
        private WindowManager mWindowManager;
    
        private Context context;
    
        public FloatWindowManager(Context context) {
            this.context = context;
        }
    
        /**
         * 创建悬浮窗
         */
        public void createFloatWindow() {
            if (floatWindowView == null) {
                floatWindowView = new FloatWindowView(MainApplication.getContext());
                getWindowManager().addView(floatWindowView, floatWindowView.smallWindowParams);
            }
        }
        /**
         * 将小悬浮窗从屏幕上移除
         **/
        public void removeFloatWindow() {
            if (floatWindowView != null) {
                WindowManager windowManager = getWindowManager();
                windowManager.removeView(floatWindowView);
                floatWindowView = null;
            }
        }
        public void setOnClickListener(View.OnClickListener listener) {
            if (floatWindowView != null) {
                floatWindowView.setOnClickListener(listener);
            }
        }
    
        /**
         * 获取WindowManager
         ** @return
         */
        private WindowManager getWindowManager() {
            if (mWindowManager == null) {
                mWindowManager = (WindowManager) context
                        .getSystemService(Context.WINDOW_SERVICE);
            }
            return mWindowManager;
        }
    }
    

    3.调用

    首先在mainfest中添加悬浮窗权限,接着构建Manager进行炫富穿的展示。

    注意在6.0以上时,对于小悬浮窗权限更为严格,需要进行动态申请,因此需要进行判断,这里只是作大致判断,更为信息请参照权限申请。

    Mainfest 权限
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 
    
      
    if (Build.VERSION.SDK_INT >= 23) {
          if(!Settings.canDrawOverlays(MainApplication.getContext())) {
            //启动Activity让用户授权
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivity(intent);
            return;
          } else {
            //执行6.0以上绘制代码
            new FloatWindowManager(MainApplication.getContext()).createFloatWindow();
          }
        } else {
          //执行6.0以下绘制代码
            new FloatWindowManager(MainApplication.getContext()).createFloatWindow();
        }
    
    
    

    Tips

    1.构造自定义View,super(Context context) 需要传入ApplicationContext,保证全局唯一的一个悬浮窗。

    2.注意权限的申请

    4.进阶-无需动态申请权限

    Android无需权限显示悬浮窗, 兼谈逆向分析app

    相关文章

      网友评论

        本文标题:悬浮窗

        本文链接:https://www.haomeiwen.com/subject/tjoizttx.html