博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
可拖拽悬浮窗、对话框悬浮窗的简单实现
阅读量:6156 次
发布时间:2019-06-21

本文共 15582 字,大约阅读时间需要 51 分钟。

  

 

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

 

一、常规悬浮窗

思路:

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

2.设置WindowManager的参数Params

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

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

 

实现:

布局文件:

 

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 *  * 重要:注意要申请权限!!!! *  
* * @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); } } }

 

记得加权限和注册服务:

  

 

二、对话框悬浮窗

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

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

布局文件:

 

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;        }    }}

 

布局文件:

 

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

 

源码下载:

 

转载地址:http://dnifa.baihongyu.com/

你可能感兴趣的文章
STM32启动过程--启动文件--分析
查看>>
垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
查看>>
淘宝的几个架构图
查看>>
linux后台运行程序
查看>>
Python异步IO --- 轻松管理10k+并发连接
查看>>
Oracle中drop user和drop user cascade的区别
查看>>
登记申请汇总
查看>>
Android Jni调用浅述
查看>>
CodeCombat森林关卡Python代码
查看>>
第一个应用程序HelloWorld
查看>>
(二)Spring Boot 起步入门(翻译自Spring Boot官方教程文档)1.5.9.RELEASE
查看>>
Shell基础之-正则表达式
查看>>
JavaScript异步之Generator、async、await
查看>>
讲讲吸顶效果与react-sticky
查看>>
c++面向对象的一些问题1 0
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
老男孩教育每日一题-第107天-简述你对***的理解,常见的有哪几种?
查看>>
Python学习--time
查看>>
在OSCHINA上的第一篇博文,以后好好学习吧
查看>>