【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

时间: 2024-10-25 22:35:36

【Anroid界面实现】通用的桌面悬浮窗口的实现(一)的相关文章

Android悬浮窗口的实现

效果图:(悬浮框可拖动) 在项目开发中有一个需求:弹出悬浮窗后,响应悬浮窗的事件再弹出对话框,但是对话框怎么也不显示.也就是说在弹出悬浮框的同时,不能再弹出对话框,可能的原因: 1.悬浮框的焦点在最前面,把对话框挡住了,我们看不到. 2.浮动框限制了对话框的弹出. 解决: 弹出对话框的时候把悬浮框关掉,然后对话框处理完了,把对话框关掉,在重新开启一个悬浮框,把需要的值传进去. 就相关知识详解: 当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫

【Anroid界面实现】WindowManager类使用详解——用户首次打开APP的使用教学蒙板效果实现

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在上一篇的文章中,我们介绍了如何实现桌面悬浮窗口,在这个效果的实现过程中,最重要的一个类就是WindowManager,今天这篇文章,将对WindowManager的使用进行介绍,并且实现一个使用WindowManager来实现用户打开APP,显示首次使用教学蒙板的效果. WindowManager类实现了ViewManager接口,ViewManager接口允许我们在Activity上添加或者是移除vi

android桌面悬浮窗实现

                        首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下类似的效果. 1.新建一个项目 , 打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.an

android桌面悬浮窗仿QQ手机管家加速效果

主要还是用到了WindowManager对桌面悬浮进行管理. 需要一个火箭的悬浮窗 一个发射台悬浮窗  ,判断火箭是否放到了发射台,如果放上了,则使用AsyTask 慢慢将火箭的图片往上移.结束后., 返回原位. 1.打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.andro

Android 悬浮窗口

一.创建悬浮窗口步骤    1.实现一个ViewGroup类,作为悬浮窗口的界面类,以便在里面重写onInterceptTouchEvent和onTouchEvent方法,实现移动界面的目的.       在本例中实现了一个FloatLayer类,可以作为通用的类,使用时需要传入WindowManager对象以实现移动窗口. // FloatLayer ~ package com.example.hellofloatingwnd; import static com.ahai.util.Debu

android 类似360悬浮窗口实现源码

当我们在手机上安装360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口).它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程.如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失.悬浮窗口的实现涉及到WindowManager(基于4.0源码分

WindowManager和WindowManager.LayoutParams的使用以及实现悬浮窗口的方法

写Android程序的时候一般用WindowManager就是去获得屏幕的宽和高,来布局一些小的东西.基本上没有怎么看他的其他的接口. 这两天想写一个简单的类似于Toast的东西,自定义布局,突然发现,原来Toast的时间是不能自己定义的,只有两个固定的时间,分别是2秒和3.5秒.我的需求是自定义显示的时间,这个显然不能满足我的需求.但是它是如何做到显示一个View凌驾于现有的所有的View之上的呢? 我们Android平台是一个又一个的Activity组成的,每一个Activity有一个或者多

(转载)简易的可拖动的桌面悬浮窗效果

本文转载自:http://www.cnblogs.com/xqxacm/p/4918470.html 首先,我们需要知道,悬浮窗分为两种:Activity级别的悬浮窗,系统级别的悬浮窗 Activity级别的悬浮窗跟随所属Activity的生命周期而变化,而系统级别的悬浮窗则可以脱离Activity而存在. 由此可知,要实现360手机卫士那样的悬浮窗效果,就需要使用系统级别的悬浮窗 下面学习实现桌面悬浮窗效果的代码步骤: 1.配置清单文件AndroidManifest.xml 中 添加系统悬浮窗

Atitit. 悬浮窗口的实现 java swing c# .net c++ js html 的实现

Atitit. 悬浮窗口的实现 java swing c# .net c++ js html 的实现 1. 建立悬浮窗口引用代码 1 1.1. 定义悬浮窗口,设置this主窗口引用,是为了在悬浮窗口中双击可缩小还还原主窗口.以及悬浮窗口右键菜单"显示主界面"中需要还原主窗 1 1.2. //设置主窗口关闭时,先关闭悬浮窗口.and系统托盘 1 1.3. 注入系统托盘图标,退出的时候儿也退出系统托盘 2 2. 悬浮窗口结构and重大的点 2 2.1. 要去掉标题栏: 2 2.2. Opa