zoukankan      html  css  js  c++  java
  • 可拖拽悬浮窗、对话框悬浮窗的简单实现

      

      本文讲解的是Android的悬浮窗机制,这个悬浮窗在很多第三方ROM会被屏蔽,像是小米,锤子上都无法显示。小米倒是可以通过开关开启,但在锤子上根本连开的机会都没有,真是无奈啊…… 虽然悬浮窗在实际中比较难以推广,但学习方面还是没问题的啦。

    一、常规悬浮窗

    思路:

    1.建立一个服务,并且在里面生成一个WindowManager对象,通过它来加载一个视图作为悬浮窗。

    2.设置WindowManager的参数Params

    3.设置一个容器来找到悬浮窗的父控件,并绑定到windowManager中去

    4.通过父控件来加载悬浮窗的视图

    实现:

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    
        <ImageButton
            android:id="@+id/floating_imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher" />
    
    </LinearLayout>

    JAVA代码:

        /**
         * 定义浮动窗口布局
         */
        LinearLayout mlayout;
        /**
         * 悬浮窗控件
         */
        ImageView mfloatingIv;
        /**
         * 悬浮窗的布局
         */
        WindowManager.LayoutParams wmParams;
        LayoutInflater inflater;
        /**
         * 创建浮动窗口设置布局参数的对象
         */
        WindowManager mWindowManager;

    1.初始化windowManager,并且找到控件

    /**
         * 初始化windowManager
         */
        private void initWindow() {
            mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
            wmParams = getParams(wmParams);//设置好悬浮窗的参数
            // 悬浮窗默认显示以左上角为起始坐标
            wmParams.gravity = Gravity.LEFT| Gravity.TOP;
            //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0        
            wmParams.x = 50;
            wmParams.y = 50;
            //得到容器,通过这个inflater来获得悬浮窗控件
            inflater = LayoutInflater.from(getApplication());
            // 获取浮动窗口视图所在布局
            mlayout = (LinearLayout) inflater.inflate(R.layout.floating_layout, null);
            // 添加悬浮窗的视图
            mWindowManager.addView(mlayout, wmParams);
        }
        
        /** 对windowManager进行设置
         * @param wmParams
         * @return
         */
        public WindowManager.LayoutParams getParams(WindowManager.LayoutParams wmParams){
            wmParams = new WindowManager.LayoutParams();
            //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
            //wmParams.type = LayoutParams.TYPE_PHONE; 
            //wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT; 
            wmParams.type = LayoutParams.TYPE_SYSTEM_ERROR; 
            //设置图片格式,效果为背景透明
            wmParams.format = PixelFormat.RGBA_8888; 
            //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
           //wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE; 
            //设置可以显示在状态栏上
            wmParams.flags =  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR|
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            
            //设置悬浮窗口长宽数据  
            wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    
            return wmParams;
        }

    2.在悬浮窗控件中设置事件

    /**
         * 找到悬浮窗的图标,并且设置事件
         * 设置悬浮窗的点击、滑动事件
         */
        private void initFloating() {
            mfloatingIv = (ImageButton) mlayout.findViewById(R.id.floating_imageView);
            mfloatingIv.getBackground().setAlpha(150);
    
            mGestureDetector = new GestureDetector(this, new MyOnGestureListener());
            //设置监听器
            mfloatingIv.setOnTouchListener(new FloatingListener());
        }
        

    3.设置悬浮窗的拖拽事件和点击事件

        //触摸监听器
        GestureDetector mGestureDetector;
       //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
        private int mTouchStartX,mTouchStartY,mTouchCurrentX,mTouchCurrentY;
        //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
        private int mStartX,mStartY,mStopX,mStopY;
        private boolean isMove;//判断悬浮窗是否移动
        
        /**
         * @author:金凯
         * @tips  :自己写的悬浮窗监听器
         * @date  :2014-3-28
         */
        private class FloatingListener implements OnTouchListener{
    
            @Override
            public boolean onTouch(View arg0, MotionEvent event) {
    
                int action = event.getAction();
                switch(action){ 
                    case MotionEvent.ACTION_DOWN:
                        isMove = false;
                        mTouchStartX = (int)event.getRawX();
                        mTouchStartY = (int)event.getRawY();
                        mStartX = (int)event.getX();
                        mStartY = (int)event.getY();
                        break; 
                    case MotionEvent.ACTION_MOVE:  
                        mTouchCurrentX = (int) event.getRawX();
                        mTouchCurrentY = (int) event.getRawY();
                        wmParams.x += mTouchCurrentX - mTouchStartX;
                        wmParams.y += mTouchCurrentY - mTouchStartY;
                        mWindowManager.updateViewLayout(mlayout, wmParams);
                        
                        mTouchStartX = mTouchCurrentX;
                        mTouchStartY = mTouchCurrentY; 
                        break;
                    case MotionEvent.ACTION_UP:
                        mStopX = (int)event.getX();
                        mStopY = (int)event.getY();
                        //System.out.println("|X| = "+ Math.abs(mStartX - mStopX));
                        //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY));
                        if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){
                            isMove = true;
                        }
                        break; 
                }
                return mGestureDetector.onTouchEvent(event);  //此处必须返回false,否则OnClickListener获取不到监听
            }
    
        }
        
        /**
         * @author:金凯
         * @tips  :自己定义的手势监听类
         * @date  :2014-3-29
         */
        class MyOnGestureListener extends SimpleOnGestureListener {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                if (!isMove) {
                    Toast.makeText(getApplicationContext(), "你点击了悬浮窗", 0).show();
                    System.out.println("onclick");
                }
                return super.onSingleTapConfirmed(e);
            }
        }

    4.服务的框架

       @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        
        @Override
        public void onCreate() {
            super.onCreate();
            initWindow();//设置窗口的参数
        }
        
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            
            initFloating();//设置悬浮窗图标
            return super.onStartCommand(intent, flags, startId);
        }
        
        @Override
        public void onDestroy() {
            super.onDestroy();
            if (mlayout != null) {    
                // 移除悬浮窗口
                mWindowManager.removeView(mlayout);
            }
        }
        

    全部代码:

    package com.kale.testfloating;
    
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.PixelFormat;
    import android.os.IBinder;
    import android.view.GestureDetector;
    import android.view.GestureDetector.SimpleOnGestureListener;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnTouchListener;
    import android.view.WindowManager;
    import android.view.WindowManager.LayoutParams;
    import android.widget.ImageButton;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.Toast;
    
    /**
     * @author:Jack Tony
     * 
     * 重要:注意要申请权限!!!!
     *  <!-- 悬浮窗的权限 -->
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     * 
     * @tips  :思路:
     * 1.获得一个windowManager类
     * 2.通过wmParams设置好windows的各种参数
     * 3.获得一个视图的容器,找到悬浮窗视图的父控件,比如linearLayout
     * 4.将父控件添加到WindowManager中去
     * 5.通过这个父控件找到要显示的悬浮窗图标,并进行拖动或点击事件的设置
     * @date  :2014-9-25
     */
    public class FloatingService extends Service{
        /**
         * 定义浮动窗口布局
         */
        LinearLayout mlayout;
        /**
         * 悬浮窗控件
         */
        ImageView mfloatingIv;
        /**
         * 悬浮窗的布局
         */
        WindowManager.LayoutParams wmParams;
        LayoutInflater inflater;
        /**
         * 创建浮动窗口设置布局参数的对象
         */
        WindowManager mWindowManager;
    
        //触摸监听器
        GestureDetector mGestureDetector;
        
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        
        @Override
        public void onCreate() {
            super.onCreate();
            initWindow();//设置窗口的参数
        }
        
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            
            initFloating();//设置悬浮窗图标
            return super.onStartCommand(intent, flags, startId);
        }
        
        @Override
        public void onDestroy() {
            super.onDestroy();
            if (mlayout != null) {    
                // 移除悬浮窗口
                mWindowManager.removeView(mlayout);
            }
        }
        
        ///////////////////////////////////////////////////////////////////////
        
        /**
         * 初始化windowManager
         */
        private void initWindow() {
            mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
            wmParams = getParams(wmParams);//设置好悬浮窗的参数
            // 悬浮窗默认显示以左上角为起始坐标
            wmParams.gravity = Gravity.LEFT| Gravity.TOP;
            //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0        
            wmParams.x = 50;
            wmParams.y = 50;
            //得到容器,通过这个inflater来获得悬浮窗控件
            inflater = LayoutInflater.from(getApplication());
            // 获取浮动窗口视图所在布局
            mlayout = (LinearLayout) inflater.inflate(R.layout.floating_layout, null);
            // 添加悬浮窗的视图
            mWindowManager.addView(mlayout, wmParams);
        }
        
        /** 对windowManager进行设置
         * @param wmParams
         * @return
         */
        public WindowManager.LayoutParams getParams(WindowManager.LayoutParams wmParams){
            wmParams = new WindowManager.LayoutParams();
            //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
            //wmParams.type = LayoutParams.TYPE_PHONE; 
            //wmParams.type = LayoutParams.TYPE_SYSTEM_ALERT; 
            wmParams.type = LayoutParams.TYPE_SYSTEM_ERROR; 
            //设置图片格式,效果为背景透明
            wmParams.format = PixelFormat.RGBA_8888; 
            //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
           //wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE; 
            //设置可以显示在状态栏上
            wmParams.flags =  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR|
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            
            //设置悬浮窗口长宽数据  
            wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    
            return wmParams;
        }
        
        /**
         * 找到悬浮窗的图标,并且设置事件
         * 设置悬浮窗的点击、滑动事件
         */
        private void initFloating() {
            mfloatingIv = (ImageButton) mlayout.findViewById(R.id.floating_imageView);
            mfloatingIv.getBackground().setAlpha(150);
    
            mGestureDetector = new GestureDetector(this, new MyOnGestureListener());
            //设置监听器
            mfloatingIv.setOnTouchListener(new FloatingListener());
        }
        
        //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
        private int mTouchStartX,mTouchStartY,mTouchCurrentX,mTouchCurrentY;
        //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
        private int mStartX,mStartY,mStopX,mStopY;
        private boolean isMove;//判断悬浮窗是否移动
        
        /**
         * @author:金凯
         * @tips  :自己写的悬浮窗监听器
         * @date  :2014-3-28
         */
        private class FloatingListener implements OnTouchListener{
    
            @Override
            public boolean onTouch(View arg0, MotionEvent event) {
    
                int action = event.getAction();
                switch(action){ 
                    case MotionEvent.ACTION_DOWN:
                        isMove = false;
                        mTouchStartX = (int)event.getRawX();
                        mTouchStartY = (int)event.getRawY();
                        mStartX = (int)event.getX();
                        mStartY = (int)event.getY();
                        break; 
                    case MotionEvent.ACTION_MOVE:  
                        mTouchCurrentX = (int) event.getRawX();
                        mTouchCurrentY = (int) event.getRawY();
                        wmParams.x += mTouchCurrentX - mTouchStartX;
                        wmParams.y += mTouchCurrentY - mTouchStartY;
                        mWindowManager.updateViewLayout(mlayout, wmParams);
                        
                        mTouchStartX = mTouchCurrentX;
                        mTouchStartY = mTouchCurrentY; 
                        break;
                    case MotionEvent.ACTION_UP:
                        mStopX = (int)event.getX();
                        mStopY = (int)event.getY();
                        //System.out.println("|X| = "+ Math.abs(mStartX - mStopX));
                        //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY));
                        if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){
                            isMove = true;
                        }
                        break; 
                }
                return mGestureDetector.onTouchEvent(event);  //此处必须返回false,否则OnClickListener获取不到监听
            }
    
        }
        
        /**
         * @author:金凯
         * @tips  :自己定义的手势监听类
         * @date  :2014-3-29
         */
        class MyOnGestureListener extends SimpleOnGestureListener {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                if (!isMove) {
                    Toast.makeText(getApplicationContext(), "你点击了悬浮窗", 0).show();
                    System.out.println("onclick");
                }
                return super.onSingleTapConfirmed(e);
            }
        }
    
        
        
    }

    记得加权限和注册服务:

        <!-- 悬浮窗的权限 -->
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
      <service android:name="com.kale.testfloating.FloatingService"/>

    二、对话框悬浮窗

    因为对话框本身就是WindowManager形成的,参数也已经设置好了,所以一般没必要再更改它的参数。

    直接贴上全部代码,大家会发现对话框的实现是相对简单的。

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/ic_launcher" />
    
    </LinearLayout>

    Java代码:

    package com.kale.testfloating;
    
    import android.app.Dialog;
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.WindowManager;
    import android.widget.ImageView;
    import android.widget.Toast;
    
    public class DialogFloatingService extends Service {
        /**
         * 定义浮动窗口布局
         */
        Dialog mDialog;
        /**
         * 悬浮窗的布局
         */
        WindowManager.LayoutParams wmParams;
        LayoutInflater inflater;
        /**
         * 创建浮动窗口设置布局参数的对象
         */
        WindowManager mWindowManager;
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO 自动生成的方法存根
            return null;
        }
        
        @Override
        public void onCreate() {
            // TODO 自动生成的方法存根
            super.onCreate();
        }
        
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // TODO 自动生成的方法存根
            initWindow();
            return super.onStartCommand(intent, flags, startId);
        }
        
        @Override
        public void onDestroy() {
            super.onDestroy();
            if(mDialog != null){
                mDialog.dismiss();
            }
        }
        
        /**
         * 初始化
         */
        private void initWindow() {
            mDialog = new Dialog(DialogFloatingService.this);
            mDialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
                
            //得到容器,通过这个inflater来获得悬浮窗控件
            inflater = LayoutInflater.from(getApplication());
            // 获取浮动窗口视图所在布局
            View view = inflater.inflate(R.layout.dialog_layout, null);
            
            ImageView iv = (ImageView)view.findViewById(R.id.imageView);
            iv.setOnClickListener(new OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    // TODO 自动生成的方法存根
                    Toast.makeText(getApplicationContext(), "ImageView onclick", 0).show();
                }
            });
            
            // 添加悬浮窗的视图
            mDialog.setContentView(view);
            mDialog.setTitle("对话框悬浮窗");
            
            mDialog.setCanceledOnTouchOutside(true);
            mDialog.show();
        }
        
        
        
    
    }

    三、使用方式

    很简单,就是开启或者关闭服务~

    package com.kale.testfloating;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        
        public void buttonListener(View v) {
            Intent intent = new Intent(MainActivity.this,FloatingService.class);
            switch (v.getId()) {
            case R.id.open_button:
                startService(intent);
                break;
            case R.id.close_button:
                stopService(intent);
                break;
            case R.id.open_dialog_button:
                startService(new Intent(MainActivity.this,DialogFloatingService.class));
                break;
            case R.id.close_dialog_button:
                stopService(new Intent(MainActivity.this,DialogFloatingService.class));
                break;
            default:
                break;
            }
        }
    }

    布局文件:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="${relativePackage}.${activityClass}" >
    
        <Button
            android:id="@+id/open_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="137dp"
            android:onClick="buttonListener"
            android:text="开启悬浮窗" />
    
        <Button
            android:id="@+id/close_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/open_button"
            android:layout_centerVertical="true"
            android:onClick="buttonListener"
            android:text="关闭悬浮窗" />
    
        <Button
            android:id="@+id/open_dialog_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/close_button"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="46dp"
            android:onClick="buttonListener"
            android:text="开启对话框悬浮窗" />
    
        <Button
            android:id="@+id/close_dialog_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/open_dialog_button"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="24dp"
            android:text="关闭对话框悬浮窗" />
    
    </RelativeLayout>

    更加牛逼的设计方案:http://weibo.com/p/1001603823661684514597

    源码下载:http://download.csdn.net/detail/shark0017/7977309

  • 相关阅读:
    screen:多重视窗管理程序
    tcpdump、nc网络工具使用
    Linux下查看系统版本号信息的方法
    python列表模拟堆栈和队列
    Linux Service and Security
    day2 列表
    Python常用方法
    Nginx1.6.0+MySQL5.6.19+PHP5.5.14(centos)
    LINUX Mysql5.6.19 安装
    python+django+pycharm 环境配置 (window7)
  • 原文地址:https://www.cnblogs.com/tianzhijiexian/p/3994546.html
Copyright © 2011-2022 走看看