首页 > 代码库 > 【Anroid界面实现】通用的桌面悬浮窗口的实现(一)
【Anroid界面实现】通用的桌面悬浮窗口的实现(一)
转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992
现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作。今天这篇文章,就是介绍如何实现桌面悬浮窗效果的。
首先,看一下效果图。
悬浮窗一共分为两个部分,一个是平常显示的小窗口,另外一个是点击小窗口显示出来的二级悬浮窗口。
首先,先看一下这个项目的目录结构。
最关键的就是红框内的四个类。
首先,FloatWindowService是一个后台的服务类,主要负责在后台不断的刷新桌面上的小悬浮窗口,否则会导致更换界面之后,悬浮窗口也会随之消失,因此需要不断的刷新。下面是实现代码。
package com.qust.floatwindow; import java.util.Timer; import java.util.TimerTask; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; /** * 悬浮窗后台服务 * * @author zhaokaiqiang * */ public class FloatWindowService extends Service { public static final String LAYOUT_RES_ID = "layoutResId"; public static final String ROOT_LAYOUT_ID = "rootLayoutId"; // 用于在线程中创建/移除/更新悬浮窗 private Handler handler = new Handler(); private Context context; private Timer timer; // 小窗口布局资源id private int layoutResId; // 布局根布局id private int rootLayoutId; @Override public int onStartCommand(Intent intent, int flags, int startId) { context = this; layoutResId = intent.getIntExtra(LAYOUT_RES_ID, 0); rootLayoutId = intent.getIntExtra(ROOT_LAYOUT_ID, 0); if (layoutResId == 0 || rootLayoutId == 0) { throw new IllegalArgumentException( "layoutResId or rootLayoutId is illegal"); } if (timer == null) { timer = new Timer(); // 每500毫秒就执行一次刷新任务 timer.scheduleAtFixedRate(new RefreshTask(), 0, 500); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // Service被终止的同时也停止定时器继续运行 timer.cancel(); timer = null; } private class RefreshTask extends TimerTask { @Override public void run() { // 当前界面没有悬浮窗显示,则创建悬浮 if (!FloatWindowManager.getInstance(context).isWindowShowing()) { handler.post(new Runnable() { @Override public void run() { FloatWindowManager.getInstance(context) .createSmallWindow(context, layoutResId, rootLayoutId); } }); } } } @Override public IBinder onBind(Intent intent) { return null; } }
除了后台服务之外,我们还需要两个自定义的布局,分别是FloatWindowSmallView和FloatWindowBigView,这两个自定义的布局,主要负责悬浮窗的前台显示,我们分别看一下代码实现。
首先是FloatWindowSmallView类的实现。
package com.qust.floatwindow; import java.lang.reflect.Field; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import com.qust.demo.ScreenUtils; import com.qust.floatingwindow.R; /** * 小悬浮窗,用于初始显示 * * @author zhaokaiqiang * */ public class FloatWindowSmallView extends LinearLayout { // 小悬浮窗的宽 public int viewWidth; // 小悬浮窗的高 public int viewHeight; // 系统状态栏的高度 private static int statusBarHeight; // 用于更新小悬浮窗的位置 private WindowManager windowManager; // 小悬浮窗的布局参数 public WindowManager.LayoutParams smallWindowParams; // 记录当前手指位置在屏幕上的横坐标 private float xInScreen; // 记录当前手指位置在屏幕上的纵坐标 private float yInScreen; // 记录手指按下时在屏幕上的横坐标,用来判断单击事件 private float xDownInScreen; // 记录手指按下时在屏幕上的纵坐标,用来判断单击事件 private float yDownInScreen; // 记录手指按下时在小悬浮窗的View上的横坐标 private float xInView; // 记录手指按下时在小悬浮窗的View上的纵坐标 private float yInView; // 单击接口 private OnClickListener listener; /** * 构造函数 * * @param context * 上下文对象 * @param layoutResId * 布局资源id * @param rootLayoutId * 根布局id */ public FloatWindowSmallView(Context context, int layoutResId, int rootLayoutId) { super(context); windowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); LayoutInflater.from(context).inflate(layoutResId, this); View view = findViewById(rootLayoutId); viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; statusBarHeight = getStatusBarHeight(); TextView percentView = (TextView) findViewById(R.id.percent); percentView.setText("悬浮窗"); smallWindowParams = new WindowManager.LayoutParams(); // 设置显示类型为phone smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE; // 显示图片格式 smallWindowParams.format = PixelFormat.RGBA_8888; // 设置交互模式 smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 设置对齐方式为左上 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP; smallWindowParams.width = viewWidth; smallWindowParams.height = viewHeight; smallWindowParams.x = ScreenUtils.getScreenWidth(context); smallWindowParams.y = ScreenUtils.getScreenHeight(context) / 2; } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度 case MotionEvent.ACTION_DOWN: // 获取相对与小悬浮窗的坐标 xInView = event.getX(); yInView = event.getY(); // 按下时的坐标位置,只记录一次 xDownInScreen = event.getRawX(); yDownInScreen = event.getRawY() - statusBarHeight; break; case MotionEvent.ACTION_MOVE: // 时时的更新当前手指在屏幕上的位置 xInScreen = event.getRawX(); yInScreen = event.getRawY() - statusBarHeight; // 手指移动的时候更新小悬浮窗的位置 updateViewPosition(); break; case MotionEvent.ACTION_UP: // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件 if (xDownInScreen == event.getRawX() && yDownInScreen == (event.getRawY() - getStatusBarHeight())) { if (listener != null) { listener.click(); } } break; } return true; } /** * 设置单击事件的回调接口 */ public void setOnClickListener(OnClickListener listener) { this.listener = listener; } /** * 更新小悬浮窗在屏幕中的位置 */ private void updateViewPosition() { smallWindowParams.x = (int) (xInScreen - xInView); smallWindowParams.y = (int) (yInScreen - yInView); windowManager.updateViewLayout(this, smallWindowParams); } /** * 获取状态栏的高度 * * @return */ private int getStatusBarHeight() { try { Class<?> c = Class.forName("com.android.internal.R$dimen"); Object o = c.newInstance(); Field field = c.getField("status_bar_height"); int x = (Integer) field.get(o); return getResources().getDimensionPixelSize(x); } catch (Exception e) { e.printStackTrace(); } return 0; } /** * 单击接口 * * @author zhaokaiqiang * */ public interface OnClickListener { public void click(); } }
在这个类里面,主要的工作是实现悬浮窗口在桌面前端的实现,还有就是位置的移动和单击事件的判断以及处理。这里使用的是主要是WindowManager类的一些方法和属性,下一篇会详细说明,这篇只说实现。
除了小悬浮窗之外,点击之后弹出的二级悬浮窗也是类似的方式添加到桌面上,下面是二级悬浮窗的代码。
package com.qust.floatwindow; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import com.qust.demo.ScreenUtils; import com.qust.floatingwindow.R; public class FloatWindowBigView extends LinearLayout { // 记录大悬浮窗的宽 public int viewWidth; // 记录大悬浮窗的高 public int viewHeight; public WindowManager.LayoutParams bigWindowParams; private Context context; public FloatWindowBigView(Context context) { super(context); this.context = context; LayoutInflater.from(context).inflate(R.layout.float_window_big, this); View view = findViewById(R.id.big_window_layout); viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; bigWindowParams = new WindowManager.LayoutParams(); // 设置显示的位置,默认的是屏幕中心 bigWindowParams.x = ScreenUtils.getScreenWidth(context) / 2 - viewWidth / 2; bigWindowParams.y = ScreenUtils.getScreenHeight(context) / 2 - viewHeight / 2; bigWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE; bigWindowParams.format = PixelFormat.RGBA_8888; // 设置交互模式 bigWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP; bigWindowParams.width = viewWidth; bigWindowParams.height = viewHeight; initView(); } private void initView() { TextView tv_back = (TextView) findViewById(R.id.tv_back); tv_back.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { FloatWindowManager.getInstance(context).removeBigWindow(); } }); } }
这些基本的类建立起来之后,剩下的就是最重要的类FloatWindowManager的实现。这个类实现的就是对悬浮窗的操作。
package com.qust.floatwindow; import android.content.Context; import android.content.Intent; import android.view.WindowManager; /** * 悬浮窗管理器 * * @author zhaokaiqiang * */ public class FloatWindowManager { // 小悬浮窗对象 private FloatWindowSmallView smallWindow; // 大悬浮窗对象 private FloatWindowBigView bigWindow; // 用于控制在屏幕上添加或移除悬浮窗 private WindowManager mWindowManager; // FloatWindowManager的单例 private static FloatWindowManager floatWindowManager; // 上下文对象 private Context context; private FloatWindowManager(Context context) { this.context = context; } public static FloatWindowManager getInstance(Context context) { if (floatWindowManager == null) { floatWindowManager = new FloatWindowManager(context); } return floatWindowManager; } /** * 创建小悬浮窗 * * @param context * 必须为应用程序的Context. */ public void createSmallWindow(Context context, int layoutResId, int rootLayoutId) { WindowManager windowManager = getWindowManager(); if (smallWindow == null) { smallWindow = new FloatWindowSmallView(context, layoutResId, rootLayoutId); windowManager.addView(smallWindow, smallWindow.smallWindowParams); } } /** * 将小悬浮窗从屏幕上移除 * * @param context */ public void removeSmallWindow() { if (smallWindow != null) { WindowManager windowManager = getWindowManager(); windowManager.removeView(smallWindow); smallWindow = null; } } public void setOnClickListener(FloatWindowSmallView.OnClickListener listener) { if (smallWindow != null) { smallWindow.setOnClickListener(listener); } } /** * 创建大悬浮窗 * * @param context * 必须为应用程序的Context. */ public void createBigWindow(Context context) { WindowManager windowManager = getWindowManager(); if (bigWindow == null) { bigWindow = new FloatWindowBigView(context); windowManager.addView(bigWindow, bigWindow.bigWindowParams); } } /** * 将大悬浮窗从屏幕上移除 * * @param context */ public void removeBigWindow() { if (bigWindow != null) { WindowManager windowManager = getWindowManager(); windowManager.removeView(bigWindow); bigWindow = null; } } public void removeAll() { context.stopService(new Intent(context, FloatWindowService.class)); removeSmallWindow(); removeBigWindow(); } /** * 是否有悬浮窗显示(包括小悬浮窗和大悬浮) * * @return 有悬浮窗显示在桌面上返回true,没有的话返回false */ public boolean isWindowShowing() { return smallWindow != null || bigWindow != null; } /** * 如果WindowManager还未创建,则创建新的WindowManager返回。否则返回当前已创建的WindowManager * * @param context * @return */ private WindowManager getWindowManager() { if (mWindowManager == null) { mWindowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); } return mWindowManager; } }
还有个获取屏幕宽高的帮助类。
package com.qust.demo; import android.content.Context; import android.view.WindowManager; /** * 屏幕帮助类 * * @author zhaokaiqiang * */ public class ScreenUtils { /** * 获取屏幕宽度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenWidth(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getWidth(); } /** * 获取屏幕宽度 * * @return */ @SuppressWarnings("deprecation") public static int getScreenHeight(Context context) { return ((WindowManager) context .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay() .getHeight(); } }
完成这些,我们就可以直接用了。
package com.qust.demo; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import com.qust.floatingwindow.R; import com.qust.floatwindow.FloatWindowManager; import com.qust.floatwindow.FloatWindowService; import com.qust.floatwindow.FloatWindowSmallView.OnClickListener; /** * 示例 * * @ClassName: com.qust.demo.MainActivity * @Description: * @author zhaokaiqiang * @date 2014-10-23 下午11:30:13 * */ public class MainActivity extends Activity { private FloatWindowManager floatWindowManager; private Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; floatWindowManager = FloatWindowManager.getInstance(context); } /** * 显示小窗口 * * @param view */ public void show(View view) { // 需要传递小悬浮窗布局,以及根布局的id,启动后台服务 Intent intent = new Intent(context, FloatWindowService.class); intent.putExtra(FloatWindowService.LAYOUT_RES_ID, R.layout.float_window_small); intent.putExtra(FloatWindowService.ROOT_LAYOUT_ID, R.id.small_window_layout); startService(intent); } /** * 显示二级悬浮窗 * * @param view */ public void showBig(View view) { // 设置小悬浮窗的单击事件 floatWindowManager.setOnClickListener(new OnClickListener() { @Override public void click() { floatWindowManager.createBigWindow(context); } }); } /** * 移除所有的悬浮窗 * * @param view */ public void remove(View view) { floatWindowManager.removeAll(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 返回键移除二级悬浮窗 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { floatWindowManager.removeBigWindow(); return true; } return super.onKeyDown(keyCode, event); } }
项目下载地址:https://github.com/ZhaoKaiQiang/FloatWindow
【Anroid界面实现】通用的桌面悬浮窗口的实现(一)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。