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

  

  本文讲解的是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

原文地址:https://www.cnblogs.com/tianzhijiexian/p/3994546.html