可拖拽悬浮窗、对话框悬浮窗的简单实现
本文讲解的是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