首页 > 代码库 > 可拖拽悬浮窗、对话框悬浮窗的简单实现

可拖拽悬浮窗、对话框悬浮窗的简单实现

  

 

  本文讲解的是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://download.csdn.net/detail/shark0017/7977309

 

可拖拽悬浮窗、对话框悬浮窗的简单实现