首页 > 代码库 > android 浮动窗口学习笔记及个人理解(仿360手机助手)

android 浮动窗口学习笔记及个人理解(仿360手机助手)

非常感谢原文作者

http://blog.csdn.net/guolin_blog/article/details/8689140

经自己理解

 

程序运行界面如下图:

1.程序入口界面

 



2.小浮动窗口

 

 

3.大浮动窗口

 

 

由上图可看出,可以看出我们基本需要:

1.一个主Activity

2.小浮动窗口view界面

3.大浮动窗口view界面

 

对于浮动窗口的管理我们还需要

4.一个Service(在后台监控管理浮动窗口的状态)

5.窗口管理类(创建/消除浮动窗口)

 

代码:

package com.ww.activity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.ww.service.FloatWindowService;

/**
 * Activity
 * 		程序主入口
 * 
 * @author wangwei
 * @Email 25501232@qq.com
 *
 */
public class MainActivity extends Activity {

	private Button btnFloatWin;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		init();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	private void init(){
		
		btnFloatWin = (Button) findViewById(R.id.btnFloatWin);
		btnFloatWin.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// 启动浮动窗口Service
				Intent intent = new Intent(MainActivity.this, FloatWindowService.class);
				startService(intent);
				finish();
			}
		});
		
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		
		
	}
	
	/**
	 * 打印日志
	 * @param msg
	 */
	private static void log(String msg) {
		Log.i("Test", msg);
	}

}

package com.ww.view;

import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
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.ww.activity.R;
import com.ww.bean.MyWindowManager;

/**
 * 小浮动窗口视图
 * 		小浮动窗口可在屏幕上自由拖动(除状态栏部分)
 * @author wangwei
 *
 */
public class FloatWindowSmallView extends LinearLayout {

	// 小浮动窗口(视图)宽、小浮动窗口(视图)高、状态栏高
	public static int viewWidth, viewHeigth, statusBarHeight;
	
	// android 窗口管理器
	private WindowManager windowManager;
	// 窗口管理器参数
	private WindowManager.LayoutParams mParams;
	
	// 移动时对应屏幕的x,y坐标;
	private float xInScreen, yInScreen;
	// 按下时对应屏幕的x,y坐标;
	private float xDownInScreen, yDownInScreen;
	// 按下时对应small_window_layout View中的x,y坐标
	private float xInView, yInView;
	
	TextView percentView;
	
	public FloatWindowSmallView(Context context) {
		super(context);
		windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		/*
		 * 查找res/layout/下的XML文件
		 * 对于一个没有被载入或者想要动态载入的界面,使用LayoutInflater.inflate()来载入
		 */
		LayoutInflater.from(context).inflate(R.layout.float_window_small, this);
		View view = findViewById(R.id.small_window_layout);
		
		/*
		 * viewWidth 计算方式 
		 * 		float_window_small ID:small_window_layout的 width heigth 为 60dp 25dp
		 * 		简单计算出像素方法,屏幕密度为160的设备 1dp=1px 
		 * 		我当前使用模拟器像素为480*800 屏幕密度为240(屏幕密度计算方式 根号内 长(像素)平方 + 宽(像素)平方 除 屏幕英寸 ) 
		 * 		240/160=1.5 
		 * 		所以对应的像素为 60dp * 1.5 = 90px
		 * 
		 * viewHeight 计算方式(与上相同)
		 * 		25dp * 1.5 = 37.5px
		 */ 
		viewWidth = view.getLayoutParams().width;
		viewHeigth = view.getLayoutParams().height;
		
		percentView = (TextView) findViewById(R.id.tvPercent);
		percentView.setText("XXX");
	}
	
	/**
	 * 触摸事件
	 * 		小浮动窗口
	 * 		1.按下时 获取各种x,y坐标
	 * 		>> 获取view中的x,y坐标
	 *         获取按下时在屏幕中的x,y坐标
	 *         获取移动时在屏幕中的x,y坐标
	 *         
	 * 		2.移动时
	 * 		>> 更新view的位置
	 * 
	 * 		3.松开时
	 * 		>> 判断按下与移动的x,y坐标是否相等,如果相等表示未移动view位置,打开大的浮动窗口
	 * 
	 * 
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 1
			xInView = event.getX();
			yInView = event.getY();
			
			xDownInScreen = event.getRawX();
			yDownInScreen = event.getRawY() - getStatusBarHeight();
			
			xInScreen = event.getRawX();
			yInScreen = event.getRawY() - getStatusBarHeight();
			break;

		case MotionEvent.ACTION_MOVE:
			// 2
			xInScreen = event.getRawX();
			// 不能移动到状态栏地方
			yInScreen = event.getRawY() - getStatusBarHeight();
			
			updateViewPosition();
			break;
			
		case MotionEvent.ACTION_UP:
			// 3
			if(xDownInScreen == xInScreen && yDownInScreen == yInScreen){
				openBigWindow();
			}
			break;
		default:
			break;
		}
		return true;
	}
	
	/**
	 * 设置viewLayout的参数
	 * 
	 * @param params
	 */
	public void setParams(WindowManager.LayoutParams params){
		mParams = params;
	}

	/**
	 * 更新view位置
	 * 		主要用来设置x,y坐标,用来改变view位置
	 */
	private void updateViewPosition(){
		mParams.x = (int) (xInScreen - xInView);
		mParams.y = (int) (yInScreen - yInView);
		windowManager.updateViewLayout(this, mParams);
		
	}
	
	/**
	 * 打开大窗口
	 * 		移动小窗口视图
	 */
	private void openBigWindow(){
		MyWindowManager.createBigWindow(getContext());
		MyWindowManager.removeSmallWindow(getContext());
	}
	
	/**
	 * 获取状态栏高度
	 * 		这里包含了两种获取方式 
	 * 		1.使用反射方式获取内部API
	 * 		2.使用正常API接口获取(推荐)
	 * 		
	 * @return
	 */
	private int getStatusBarHeight(){
		Rect frame = new Rect();
		this.getWindowVisibleDisplayFrame(frame);
		// 状态栏高度
		statusBarHeight = frame.top;
		
		/*
		 * 获取应用的标题栏高度
		 * 因没有应用界面,所以下面代码会获取失败,在此项目中也无需获取该值
		 */
		// int contentTop = this.findViewById(Window.ID_ANDROID_CONTENT).getTop();
		// int titleBarHeight = contentTop - top;
		// log("titleBarHeight>>>"+titleBarHeight);
		
		/*
		 * 使用反射的方法调用内部API获取状态栏高度
		 * if(statusBarHeight == 0){
			try {
				Class<?> c = Class.forName("com.android.internal.R$dimen");
				Object o = c.newInstance();
				Field field = c.getField("status_bar_height");
				int x = field.getInt(o);
				statusBarHeight = getResources().getDimensionPixelOffset(x);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (NoSuchFieldException e) {
				e.printStackTrace();
			}
		}*/
		log("statusBarHeight >> "+statusBarHeight);
		return statusBarHeight;
	}
	
	/**
	 * 打印日志
	 * @param msg
	 */
	private static void log(String msg) {
		Log.i("Test", msg);
	}
}

<pre name="code" class="java">package com.ww.bean;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.util.Log;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.TextView;

import com.ww.activity.R;
import com.ww.view.FloatWindowBigView;
import com.ww.view.FloatWindowSmallView;

/**
 * 窗口管理
 * @author wangwei
 *
 */
public class MyWindowManager {
	
	// 大浮动窗口
	private static FloatWindowSmallView smallWindow;
	// 小浮动窗口
	private static FloatWindowBigView bigWindow;
	// 小浮动窗口参数、大浮动窗口参数 
	private static LayoutParams smallWindowParams, bigWindowParams;
	// 窗口管理 
	private static WindowManager mWindowManager;
	
	private static ActivityManager mActivityManager;
	
	/**
	 * 创建小浮动窗口
	 * @param context
	 */
	public static void createSmallWindow(Context context){
		WindowManager windowManager = getWindowManager(context);
		/*
		 * 获取屏幕width和height 像素方法
		 * minSdkVersion 13 以上使用此方法
		 */
		Point p = new Point();
		getWindowManager(context).getDefaultDisplay().getSize(p);
		int screenWidth = p.x;
		int screenHeigth = p.y;
		
		/*
		 * 另一种获取屏幕width和height 像素方法(已过时)
		 * 
		int screenWidth = windowManager.getDefaultDisplay().getWidth();
		int screenHeigth = windowManager.getDefaultDisplay().getHeight();*/
		
		if(smallWindow == null){
			smallWindow = new FloatWindowSmallView(context);
			if(smallWindowParams  == null){
				smallWindowParams = new LayoutParams();
				// 它置于所有应用程序之上,状态栏之下
				smallWindowParams.type = LayoutParams.TYPE_PHONE;
				// 位图格式
				smallWindowParams.format = PixelFormat.RGBA_8888;
				/*
				 * 行为选项,默认为 none
				 * 当前窗口可以获得焦点 | 不接受触摸屏事件
				 * 不接受浮动窗口之外的点击事件
				 */
				smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
				// 浮动窗口停靠
				smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
				// 浮动窗口宽
				smallWindowParams.width = FloatWindowSmallView.viewWidth;
				// 浮动窗口高
				smallWindowParams.height = FloatWindowSmallView.viewHeigth;
				// 浮动窗口x坐标
				smallWindowParams.x = screenWidth;
				// 浮动窗口y坐标
				smallWindowParams.y = screenHeigth / 2;
			}
			smallWindow.setParams(smallWindowParams);
			// 将小浮动窗口及浮动窗口参数添加视窗中
			windowManager.addView(smallWindow, smallWindowParams);
		}
		
	}
	
	/**
	 * 移动小浮动窗口
	 * @param context
	 */
	public static void removeSmallWindow(Context context){
		if(smallWindow != null){
			WindowManager windowManager = getWindowManager(context);
			windowManager.removeView(smallWindow);
			smallWindow = null;
		}
	}
	
	/**
	 * 创建大浮动窗口
	 * @param context
	 */
	public static void createBigWindow(Context context){
		WindowManager windowManager = getWindowManager(context);
		int screenWidth = windowManager.getDefaultDisplay().getWidth();
		int screenHeigth = windowManager.getDefaultDisplay().getHeight();
		if(bigWindow == null){
			bigWindow = new FloatWindowBigView(context);
			if(bigWindowParams == null){
				/*
				 * 参数说明与创建小浮动窗口类似
				 * 详见createSmallWindow
				 */
				bigWindowParams = new LayoutParams();
				bigWindowParams.x = screenWidth / 2 - FloatWindowBigView.viewWidth / 2;
				bigWindowParams.y = screenHeigth / 2 - FloatWindowBigView.viewHeight / 2;
				bigWindowParams.type = LayoutParams.TYPE_PHONE;
				bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
				bigWindowParams.width = FloatWindowBigView.viewWidth;
				bigWindowParams.height = FloatWindowBigView.viewHeight;
			}
			windowManager.addView(bigWindow, bigWindowParams);
		}
	}
	
	/**
	 * 移动大浮动窗口
	 * @param context
	 */
	public static void removeBigWindow(Context context){
		if(bigWindow != null){
			WindowManager windowManager = getWindowManager(context);
			windowManager.removeView(bigWindow);
			bigWindow = null;
		}
	}
	
	/**
	 * 更新内存使用率
	 * @param context
	 */
	public static void updateUsedPercent(Context context){
		if(smallWindow != null){
			TextView percentView = (TextView) smallWindow.findViewById(R.id.tvPercent);
			percentView.setText(getUsedPercentValue(context));
		}
	}
	
	/**
	 * 判断小浮动窗口或大浮动窗口是否显示 
	 * @return
	 */
	public static boolean isWindowShowing(){
		return smallWindow !=null || bigWindow != null;
	}
	
	/**
	 * 获取 WindowManager 对象
	 * @param context
	 * @return
	 */
	private static WindowManager getWindowManager(Context context){
		if(mWindowManager == null){
			mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		}
		return mWindowManager;
	}
	
	/**
	 * 获取 ActivityManager 对象
	 * @param context
	 * @return
	 */
	private static ActivityManager getActivityManager(Context context){
		if(mActivityManager == null){
			mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		}
		return mActivityManager;
	}
	
	/**
	 * 获取 内存 使用率
	 * @param context
	 * @return 内存占用百分比
	 */
	public static String getUsedPercentValue(Context context){
		String dir = "/proc/meminfo";
		try {
			FileReader fr = new FileReader(dir);
			BufferedReader br = new BufferedReader(fr, 2048);
			String memoryLine = br.readLine();
			String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal"));
			br.close();
			// 总内存
			long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
			// 当前剩余内存
			long availableSize = getAvailableMemory(context) / 1024;
			
//			log(totalMemorySize + "----" + availableSize);
			// 已用内存百分比
			int percent = (int)((totalMemorySize - availableSize) / (float)totalMemorySize * 100);
			
			return percent + "%";
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "悬浮窗";
	}
	
	/**
	 * 获取 当前剩余内存 
	 * 
	 * 想使用mi.totalMem获取总内存,但报错,不知道为何
	 * 
	 * @param context
	 * @return
	 */
	private static long getAvailableMemory(Context context){
		ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
		getActivityManager(context).getMemoryInfo(mi);
		return mi.availMem;
	}

	/**
	 * 打印日志
	 * @param msg
	 */
	private static void log(String msg) {
		Log.i("Test", msg);
	}
	
}


package com.ww.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.ww.bean.MyWindowManager;

/**
 * Service
 * 		浮动窗口Service 
 * 		使用定时任务监控管理浮动窗口的状态
 * 		大小浮动窗口的创建移动及窗口位置管理
 * 
 * @author wangwei
 *
 */
public class FloatWindowService extends Service {

	private Handler handler = new Handler();
	
	private Timer timer;
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
	
	/**
	 * 启动服务(服务启动时)
	 * 		调度定时任务
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		if(timer == null){
			timer = new Timer();
			timer.scheduleAtFixedRate(new RefreshTask(), flags, startId);
		}
		return super.onStartCommand(intent, flags, startId);
	}
	
	/**
	 * 服务注销(服务停止时)
	 * 		1.取消定时任务高度
	 * 		2.将timer定时器设置为null
	 */
	@Override
	public void onDestroy() {
		super.onDestroy();
		timer.cancel();
		timer = null;
	}
	
	/**
	 * 
	 * 定时任务执行线程
	 * 		有三种情况
	 * 		1.当前在HOME界面,并且浮动窗口(大窗口和小窗口)没有显示
	 * 		>> 创建小窗口
	 * 
	 * 		2.当前不在HOME界面,并且浮动窗口(大窗口或小窗口)已显示
	 * 		>> 移动小窗口或大窗口
	 * 
	 * 		3.当前在HOME界面,并且浮动窗口(大窗口或小窗口)已显示
	 * 		>> 更新窗口位置
	 * 
	 * @author wangwei
	 * @date 2014-6-13
	 */
	class RefreshTask extends TimerTask{

		@Override
		public void run() {
			if(isHome() && !MyWindowManager.isWindowShowing()){
				// 1 
				handler.post(new Runnable() {
					@Override
					public void run() {
						MyWindowManager.createSmallWindow(getApplicationContext());
					}
				});
			}else if(!isHome() && MyWindowManager.isWindowShowing()){
				// 2
				handler.post(new Runnable() {
					
					@Override
					public void run() {
						MyWindowManager.removeSmallWindow(getApplicationContext());
						MyWindowManager.removeBigWindow(getApplicationContext());
					}
				});
			}else if(isHome() && MyWindowManager.isWindowShowing()){
				// 3
				handler.post(new Runnable() {
					@Override
					public void run() {
						MyWindowManager.updateUsedPercent(getApplicationContext());
					}
				});
			}
		}
		
	}
	
	/**
	 * 是否在Home界面 
	 * @return
	 */
	private boolean isHome(){
		// Activity管理器
		ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		/*
		 * 获得当前正在运行的任务 
		 * 返回最多任务数
		 * mActivityManager.getRunningTasks(maxNum);  
		 * 这里1就够了 得到的即为当前正在运行(可见)的任务
		 */
		List<RunningTaskInfo> listRti = mActivityManager.getRunningTasks(1);
		return getHomes().contains(listRti.get(0).topActivity.getPackageName());
	}
	
	/**
	 * 得到所有的Home界面
	 * @return Home应用的包名
	 */
	private List<String> getHomes(){
		List<String> names = new ArrayList<String>();
		// 包管理器
		PackageManager packageManager = this.getPackageManager();
		Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.addCategory(Intent.CATEGORY_HOME);
		// 查找出属于桌面应用的列表 
		List<ResolveInfo> listRi = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
		for (ResolveInfo ri : listRi) {
			names.add(ri.activityInfo.packageName);
		}
		return names;
	}

	
	/**
	 * 打印日志
	 * @param msg
	 */
	private static void log(String msg) {
		Log.i("Test", msg);
	}
	
}




如需要原码,可在评论下留下邮箱地址