非常感谢原文作者
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); } }
如需要原码,可在评论下留下邮箱地址