大家好,今天给大家带来一个仿360手机卫士悬浮窗效果的教程,在开始之前请允许我说几句不相干的废话。

不知不觉我发现自己接触Android已有近三个年头了,期间各种的成长少不了各位高手的帮助,总是有很多高手喜欢把自己的经验写在网上,供大家来学习,我也是从中受惠了很多,在此我深表感谢。可是我发现我却从来没有将自己平时的一些心得拿出来与大家分享,共同学习,太没有奉献精神了。于是我痛定思痛,决定从今天开始写博客,希望可以指点在我后面的开发者,更快地进入Android开发者的行列当中。

好了,废话就说这么多,下面开始进入今天的主题吧。

360手机卫士我相信大家都知道,好多人手机上都会装这一款软件,那么我们对它的一个桌面悬浮窗效果想必都不会陌生。请看下图:

                       

首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速。好,我们现在就来模拟实现一下类似的效果。

先谈一下基本的实现原理,这种桌面悬浮窗的效果很类似与Widget,但是它比Widget要灵活的多。主要是通过WindowManager这个类来实现的,调用这个类的addView方法用于添加一个悬浮窗,updateViewLayout方法用于更新悬浮窗的参数,removeView用于移除悬浮窗。其中悬浮窗的参数有必要详细说明一下。

WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:

type值用于确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。

flags值用于确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。

gravity值用于确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。

x值用于确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。

y值用于确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。

width值用于指定悬浮窗的宽度。

height值用于指定悬浮窗的高度。

创建悬浮窗这种窗体需要向用户申请权限才可以的,因此还需要在AndroidManifest.xml中加入<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

原理介绍完了,下面我们开始用代码实现。首先在Eclipse中新建一个Android项目,项目名就叫做360FloatWindowDemo。然后写一下布局文件,布局文件非常简单,只有一个按钮,打开或新建activity_main.xml,加入如下代码:

[html] view
plain
copy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. tools:context=".MainActivity" >
  6. <Button
  7. android:id="@+id/start_float_window"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="Start Float Window" >
  11. </Button>
  12. </RelativeLayout>

然后再新建一个名为float_window_small.xml的布局文件,用于做为小悬浮窗的布局,在其中加入如下代码:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:id="@+id/small_window_layout"
  5. android:layout_width="60dip"
  6. android:layout_height="25dip"
  7. android:background="@drawable/bg_small"
  8. >
  9. <TextView
  10. android:id="@+id/percent"
  11. android:layout_width="fill_parent"
  12. android:layout_height="fill_parent"
  13. android:gravity="center"
  14. android:textColor="#ffffff"
  15. />
  16. </LinearLayout>

再新建一个名为float_window_big.xml的布局文件,用于做为大悬浮窗的布局,在其中加入如下代码:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:id="@+id/big_window_layout"
  5. android:layout_width="200dip"
  6. android:layout_height="100dip"
  7. android:background="@drawable/bg_big"
  8. android:orientation="vertical"
  9. >
  10. <Button
  11. android:id="@+id/close"
  12. android:layout_width="100dip"
  13. android:layout_height="40dip"
  14. android:layout_gravity="center_horizontal"
  15. android:layout_marginTop="12dip"
  16. android:text="关闭悬浮窗"
  17. />
  18. <Button
  19. android:id="@+id/back"
  20. android:layout_width="100dip"
  21. android:layout_height="40dip"
  22. android:layout_gravity="center_horizontal"
  23. android:text="返回"
  24. />
  25. </LinearLayout>

两个悬浮窗布局文件中用到的图片资源,大家可以随便找点图片来代替,同时我会给出源码,大家也可以从源码中取出。

然后打开或创建MainActivity,这是项目的主界面,在里面加入如下代码:

[java] view
plain
copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. Button startFloatWindow = (Button) findViewById(R.id.start_float_window);
  7. startFloatWindow.setOnClickListener(new OnClickListener() {
  8. @Override
  9. public void onClick(View arg0) {
  10. Intent intent = new Intent(MainActivity.this, FloatWindowService.class);
  11. startService(intent);
  12. finish();
  13. }
  14. });
  15. }
  16. }

这里可以看到,MainActivity的代码非窗简单,就是对开启悬浮窗的按钮注册了一个点击事件,用于打开一个服务,然后关闭当前Activity。创建悬浮窗的逻辑都交给服务去做了。好,现在我们来创建这个服务。新建一个名为FloatWindowService的类,这个类继承自Service,在里面加入如下代码:

[java] view
plain
copy

  1. public class FloatWindowService extends Service {
  2. /**
  3. * 用于在线程中创建或移除悬浮窗。
  4. */
  5. private Handler handler = new Handler();
  6. /**
  7. * 定时器,定时进行检测当前应该创建还是移除悬浮窗。
  8. */
  9. private Timer timer;
  10. @Override
  11. public IBinder onBind(Intent intent) {
  12. return null;
  13. }
  14. @Override
  15. public int onStartCommand(Intent intent, int flags, int startId) {
  16. // 开启定时器,每隔0.5秒刷新一次
  17. if (timer == null) {
  18. timer = new Timer();
  19. timer.scheduleAtFixedRate(new RefreshTask(), 0, 500);
  20. }
  21. return super.onStartCommand(intent, flags, startId);
  22. }
  23. @Override
  24. public void onDestroy() {
  25. super.onDestroy();
  26. // Service被终止的同时也停止定时器继续运行
  27. timer.cancel();
  28. timer = null;
  29. }
  30. class RefreshTask extends TimerTask {
  31. @Override
  32. public void run() {
  33. // 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。
  34. if (isHome() && !MyWindowManager.isWindowShowing()) {
  35. handler.post(new Runnable() {
  36. @Override
  37. public void run() {
  38. MyWindowManager.createSmallWindow(getApplicationContext());
  39. }
  40. });
  41. }
  42. // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。
  43. else if (!isHome() && MyWindowManager.isWindowShowing()) {
  44. handler.post(new Runnable() {
  45. @Override
  46. public void run() {
  47. MyWindowManager.removeSmallWindow(getApplicationContext());
  48. MyWindowManager.removeBigWindow(getApplicationContext());
  49. }
  50. });
  51. }
  52. // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。
  53. else if (isHome() && MyWindowManager.isWindowShowing()) {
  54. handler.post(new Runnable() {
  55. @Override
  56. public void run() {
  57. MyWindowManager.updateUsedPercent(getApplicationContext());
  58. }
  59. });
  60. }
  61. }
  62. }
  63. /**
  64. * 判断当前界面是否是桌面
  65. */
  66. private boolean isHome() {
  67. ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
  68. List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
  69. return getHomes().contains(rti.get(0).topActivity.getPackageName());
  70. }
  71. /**
  72. * 获得属于桌面的应用的应用包名称
  73. *
  74. * @return 返回包含所有包名的字符串列表
  75. */
  76. private List<String> getHomes() {
  77. List<String> names = new ArrayList<String>();
  78. PackageManager packageManager = this.getPackageManager();
  79. Intent intent = new Intent(Intent.ACTION_MAIN);
  80. intent.addCategory(Intent.CATEGORY_HOME);
  81. List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
  82. PackageManager.MATCH_DEFAULT_ONLY);
  83. for (ResolveInfo ri : resolveInfo) {
  84. names.add(ri.activityInfo.packageName);
  85. }
  86. return names;
  87. }
  88. }

FloatWindowService的onStartCommand方法中开启了一个定时器,每隔500毫秒就会执行RefreshTask。在RefreshTask当中,要进行判断,如果手机当前是在桌面的话,就应该显示悬浮窗,如果手机打开了某一个应用程序,就应该移除悬浮窗,如果手机在桌面的话,还应该更新内存使用百分比的数据。而当FloatWindowService被销毁的时候,应该将定时器停止,否则它还会一直运行。

从上面的代码我们也可以看出,创建和移除悬浮窗,以及更新悬浮窗内的数据,都是由MyWindowManager这个类来管理的,比起直接把这些代码写在Activity或Service当中,使用一个专门的工具类来管理要好的多。不过要想创建悬浮窗,还是先要把悬浮窗的View写出来。

新建一个名叫FloatWindowSmallView的类,继承自LinearLayout。新建一个名叫FloatWindowBigView的类,也继承自LinearLayout。

在FloatWindowSmallView中加入如下代码:

[java] view
plain
copy

  1. public class FloatWindowSmallView extends LinearLayout {
  2. /**
  3. * 记录小悬浮窗的宽度
  4. */
  5. public static int viewWidth;
  6. /**
  7. * 记录小悬浮窗的高度
  8. */
  9. public static int viewHeight;
  10. /**
  11. * 记录系统状态栏的高度
  12. */
  13. private static int statusBarHeight;
  14. /**
  15. * 用于更新小悬浮窗的位置
  16. */
  17. private WindowManager windowManager;
  18. /**
  19. * 小悬浮窗的参数
  20. */
  21. private WindowManager.LayoutParams mParams;
  22. /**
  23. * 记录当前手指位置在屏幕上的横坐标值
  24. */
  25. private float xInScreen;
  26. /**
  27. * 记录当前手指位置在屏幕上的纵坐标值
  28. */
  29. private float yInScreen;
  30. /**
  31. * 记录手指按下时在屏幕上的横坐标的值
  32. */
  33. private float xDownInScreen;
  34. /**
  35. * 记录手指按下时在屏幕上的纵坐标的值
  36. */
  37. private float yDownInScreen;
  38. /**
  39. * 记录手指按下时在小悬浮窗的View上的横坐标的值
  40. */
  41. private float xInView;
  42. /**
  43. * 记录手指按下时在小悬浮窗的View上的纵坐标的值
  44. */
  45. private float yInView;
  46. public FloatWindowSmallView(Context context) {
  47. super(context);
  48. windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  49. LayoutInflater.from(context).inflate(R.layout.float_window_small, this);
  50. View view = findViewById(R.id.small_window_layout);
  51. viewWidth = view.getLayoutParams().width;
  52. viewHeight = view.getLayoutParams().height;
  53. TextView percentView = (TextView) findViewById(R.id.percent);
  54. percentView.setText(MyWindowManager.getUsedPercentValue(context));
  55. }
  56. @Override
  57. public boolean onTouchEvent(MotionEvent event) {
  58. switch (event.getAction()) {
  59. case MotionEvent.ACTION_DOWN:
  60. // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
  61. xInView = event.getX();
  62. yInView = event.getY();
  63. xDownInScreen = event.getRawX();
  64. yDownInScreen = event.getRawY() - getStatusBarHeight();
  65. xInScreen = event.getRawX();
  66. yInScreen = event.getRawY() - getStatusBarHeight();
  67. break;
  68. case MotionEvent.ACTION_MOVE:
  69. xInScreen = event.getRawX();
  70. yInScreen = event.getRawY() - getStatusBarHeight();
  71. // 手指移动的时候更新小悬浮窗的位置
  72. updateViewPosition();
  73. break;
  74. case MotionEvent.ACTION_UP:
  75. // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
  76. if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
  77. openBigWindow();
  78. }
  79. break;
  80. default:
  81. break;
  82. }
  83. return true;
  84. }
  85. /**
  86. * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
  87. *
  88. * @param params
  89. *            小悬浮窗的参数
  90. */
  91. public void setParams(WindowManager.LayoutParams params) {
  92. mParams = params;
  93. }
  94. /**
  95. * 更新小悬浮窗在屏幕中的位置。
  96. */
  97. private void updateViewPosition() {
  98. mParams.x = (int) (xInScreen - xInView);
  99. mParams.y = (int) (yInScreen - yInView);
  100. windowManager.updateViewLayout(this, mParams);
  101. }
  102. /**
  103. * 打开大悬浮窗,同时关闭小悬浮窗。
  104. */
  105. private void openBigWindow() {
  106. MyWindowManager.createBigWindow(getContext());
  107. MyWindowManager.removeSmallWindow(getContext());
  108. }
  109. /**
  110. * 用于获取状态栏的高度。
  111. *
  112. * @return 返回状态栏高度的像素值。
  113. */
  114. private int getStatusBarHeight() {
  115. if (statusBarHeight == 0) {
  116. try {
  117. Class<?> c = Class.forName("com.android.internal.R$dimen");
  118. Object o = c.newInstance();
  119. Field field = c.getField("status_bar_height");
  120. int x = (Integer) field.get(o);
  121. statusBarHeight = getResources().getDimensionPixelSize(x);
  122. } catch (Exception e) {
  123. e.printStackTrace();
  124. }
  125. }
  126. return statusBarHeight;
  127. }

其中,对这个View的onTouchEvent事件进行了重写,用于实现拖动和点击的效果。如果发现用户触发了ACTION_DOWN事件,会记录按下时的坐标等数据。如果发现用户触发了ACTION_MOVE事件,则根据当前移动的坐标更新悬浮窗在屏幕中的位置。如果发现用户触发了ACTION_UP事件,会和ACTION_DOWN中记下的坐标对比,如果发现是相同的,则视为用户对悬浮窗进行了点击。点击小悬浮窗则打开大悬浮窗,然后我们来实现大悬浮窗的View。

在FloatWindowBigView中加入如下代码:

[java] view
plain
copy

  1. public class FloatWindowBigView extends LinearLayout {
  2. /**
  3. * 记录大悬浮窗的宽度
  4. */
  5. public static int viewWidth;
  6. /**
  7. * 记录大悬浮窗的高度
  8. */
  9. public static int viewHeight;
  10. public FloatWindowBigView(final Context context) {
  11. super(context);
  12. LayoutInflater.from(context).inflate(R.layout.float_window_big, this);
  13. View view = findViewById(R.id.big_window_layout);
  14. viewWidth = view.getLayoutParams().width;
  15. viewHeight = view.getLayoutParams().height;
  16. Button close = (Button) findViewById(R.id.close);
  17. Button back = (Button) findViewById(R.id.back);
  18. close.setOnClickListener(new OnClickListener() {
  19. @Override
  20. public void onClick(View v) {
  21. // 点击关闭悬浮窗的时候,移除所有悬浮窗,并停止Service
  22. MyWindowManager.removeBigWindow(context);
  23. MyWindowManager.removeSmallWindow(context);
  24. Intent intent = new Intent(getContext(), FloatWindowService.class);
  25. context.stopService(intent);
  26. }
  27. });
  28. back.setOnClickListener(new OnClickListener() {
  29. @Override
  30. public void onClick(View v) {
  31. // 点击返回的时候,移除大悬浮窗,创建小悬浮窗
  32. MyWindowManager.removeBigWindow(context);
  33. MyWindowManager.createSmallWindow(context);
  34. }
  35. });
  36. }
  37. }

比起FloatWindowSmallView,FloatWindowBigView要简单的多,其中只有两个按钮,点击close按钮,将悬浮窗全部移除,并将Service终止。单击back按钮则移除大悬浮窗,重新创建小悬浮窗。

现在两个悬浮窗的View都已经写好了,我们来创建MyWindowManager,代码如下:

[java] view
plain
copy

  1. public class MyWindowManager {
  2. /**
  3. * 小悬浮窗View的实例
  4. */
  5. private static FloatWindowSmallView smallWindow;
  6. /**
  7. * 大悬浮窗View的实例
  8. */
  9. private static FloatWindowBigView bigWindow;
  10. /**
  11. * 小悬浮窗View的参数
  12. */
  13. private static LayoutParams smallWindowParams;
  14. /**
  15. * 大悬浮窗View的参数
  16. */
  17. private static LayoutParams bigWindowParams;
  18. /**
  19. * 用于控制在屏幕上添加或移除悬浮窗
  20. */
  21. private static WindowManager mWindowManager;
  22. /**
  23. * 用于获取手机可用内存
  24. */
  25. private static ActivityManager mActivityManager;
  26. /**
  27. * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
  28. *
  29. * @param context
  30. *            必须为应用程序的Context.
  31. */
  32. public static void createSmallWindow(Context context) {
  33. WindowManager windowManager = getWindowManager(context);
  34. int screenWidth = windowManager.getDefaultDisplay().getWidth();
  35. int screenHeight = windowManager.getDefaultDisplay().getHeight();
  36. if (smallWindow == null) {
  37. smallWindow = new FloatWindowSmallView(context);
  38. if (smallWindowParams == null) {
  39. smallWindowParams = new LayoutParams();
  40. smallWindowParams.type = LayoutParams.TYPE_PHONE;
  41. smallWindowParams.format = PixelFormat.RGBA_8888;
  42. smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
  43. | LayoutParams.FLAG_NOT_FOCUSABLE;
  44. smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
  45. smallWindowParams.width = FloatWindowSmallView.viewWidth;
  46. smallWindowParams.height = FloatWindowSmallView.viewHeight;
  47. smallWindowParams.x = screenWidth;
  48. smallWindowParams.y = screenHeight / 2;
  49. }
  50. smallWindow.setParams(smallWindowParams);
  51. windowManager.addView(smallWindow, smallWindowParams);
  52. }
  53. }
  54. /**
  55. * 将小悬浮窗从屏幕上移除。
  56. *
  57. * @param context
  58. *            必须为应用程序的Context.
  59. */
  60. public static void removeSmallWindow(Context context) {
  61. if (smallWindow != null) {
  62. WindowManager windowManager = getWindowManager(context);
  63. windowManager.removeView(smallWindow);
  64. smallWindow = null;
  65. }
  66. }
  67. /**
  68. * 创建一个大悬浮窗。位置为屏幕正中间。
  69. *
  70. * @param context
  71. *            必须为应用程序的Context.
  72. */
  73. public static void createBigWindow(Context context) {
  74. WindowManager windowManager = getWindowManager(context);
  75. int screenWidth = windowManager.getDefaultDisplay().getWidth();
  76. int screenHeight = windowManager.getDefaultDisplay().getHeight();
  77. if (bigWindow == null) {
  78. bigWindow = new FloatWindowBigView(context);
  79. if (bigWindowParams == null) {
  80. bigWindowParams = new LayoutParams();
  81. bigWindowParams.x = screenWidth / 2 - FloatWindowBigView.viewWidth / 2;
  82. bigWindowParams.y = screenHeight / 2 - FloatWindowBigView.viewHeight / 2;
  83. bigWindowParams.type = LayoutParams.TYPE_PHONE;
  84. bigWindowParams.format = PixelFormat.RGBA_8888;
  85. bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
  86. bigWindowParams.width = FloatWindowBigView.viewWidth;
  87. bigWindowParams.height = FloatWindowBigView.viewHeight;
  88. }
  89. windowManager.addView(bigWindow, bigWindowParams);
  90. }
  91. }
  92. /**
  93. * 将大悬浮窗从屏幕上移除。
  94. *
  95. * @param context
  96. *            必须为应用程序的Context.
  97. */
  98. public static void removeBigWindow(Context context) {
  99. if (bigWindow != null) {
  100. WindowManager windowManager = getWindowManager(context);
  101. windowManager.removeView(bigWindow);
  102. bigWindow = null;
  103. }
  104. }
  105. /**
  106. * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
  107. *
  108. * @param context
  109. *            可传入应用程序上下文。
  110. */
  111. public static void updateUsedPercent(Context context) {
  112. if (smallWindow != null) {
  113. TextView percentView = (TextView) smallWindow.findViewById(R.id.percent);
  114. percentView.setText(getUsedPercentValue(context));
  115. }
  116. }
  117. /**
  118. * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
  119. *
  120. * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
  121. */
  122. public static boolean isWindowShowing() {
  123. return smallWindow != null || bigWindow != null;
  124. }
  125. /**
  126. * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
  127. *
  128. * @param context
  129. *            必须为应用程序的Context.
  130. * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
  131. */
  132. private static WindowManager getWindowManager(Context context) {
  133. if (mWindowManager == null) {
  134. mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  135. }
  136. return mWindowManager;
  137. }
  138. /**
  139. * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。
  140. *
  141. * @param context
  142. *            可传入应用程序上下文。
  143. * @return ActivityManager的实例,用于获取手机可用内存。
  144. */
  145. private static ActivityManager getActivityManager(Context context) {
  146. if (mActivityManager == null) {
  147. mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  148. }
  149. return mActivityManager;
  150. }
  151. /**
  152. * 计算已使用内存的百分比,并返回。
  153. *
  154. * @param context
  155. *            可传入应用程序上下文。
  156. * @return 已使用内存的百分比,以字符串形式返回。
  157. */
  158. public static String getUsedPercentValue(Context context) {
  159. String dir = "/proc/meminfo";
  160. try {
  161. FileReader fr = new FileReader(dir);
  162. BufferedReader br = new BufferedReader(fr, 2048);
  163. String memoryLine = br.readLine();
  164. String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
  165. br.close();
  166. long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
  167. long availableSize = getAvailableMemory(context) / 1024;
  168. int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);
  169. return percent + "%";
  170. } catch (IOException e) {
  171. e.printStackTrace();
  172. }
  173. return "悬浮窗";
  174. }
  175. /**
  176. * 获取当前可用内存,返回数据以字节为单位。
  177. *
  178. * @param context
  179. *            可传入应用程序上下文。
  180. * @return 当前可用内存。
  181. */
  182. private static long getAvailableMemory(Context context) {
  183. ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
  184. getActivityManager(context).getMemoryInfo(mi);
  185. return mi.availMem;
  186. }
  187. }

这个类负责了控制大悬浮窗,小悬浮窗的创建和移除,系统内存使用百分比的计算等操作。

到这里基本所有的代码都已经写完了,然后我们来看一下AndroidManifest.xml文件吧,里面代码如下:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.demo.floatwindowdemo"
  4. android:versionCode="1"
  5. android:versionName="1.0" >
  6. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  7. <uses-sdk
  8. android:minSdkVersion="8"
  9. android:targetSdkVersion="8" />
  10. <application
  11. android:allowBackup="true"
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name"
  14. android:theme="@style/AppTheme" >
  15. <activity
  16. android:name="com.demo.floatwindowdemo.MainActivity"
  17. android:label="@string/app_name" >
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20. <category android:name="android.intent.category.LAUNCHER" />
  21. </intent-filter>
  22. </activity>
  23. <service android:name=".FloatWindowService"></service>
  24. </application>
  25. </manifest>

比较简单,记得把Activity和Service在里面注册好,还有一个权限声明需要添加的android.permission.SYSTEM_ALERT_WINDOW,表示需要用户授权允许创建系统提示窗口,也就是我们的桌面悬浮窗。

好了,现在让我们运行一下项目吧,效果如下图,主界面只有一个简单的按钮,点击按钮后,Activity被关闭,小悬浮窗显示在桌面上。其中显示着当前内存使用的百分比。

                     

小悬浮窗是可以自由拖动的,如果打开了其它的应用程序,小悬浮窗会自动隐藏,回到桌面后小悬浮窗又会显示出来。

                   

如果点击了小悬浮窗会弹出大悬浮窗来,这里我们大悬浮窗做的比较简单,就只有两个按钮。大悬浮窗展示的时候手机的所有其它程序是不可点的,因为焦点都在悬浮窗上了。点击返回按钮会重新展示小悬浮窗,点击关闭悬浮窗按钮,Service也会一起停掉。

360手机卫士的一键加速功能我们就不做了,就像独孤九剑一样,重要的是剑意而不是剑招,我相信大家学会了创建悬浮窗的基本原理后可以做出比360更有创意的东西。

如果大家还有什么疑问的,请在下面留言。

源码下载,请点击这里

补充:

有朋友跟我反应,上面的代码在Android 3.0以上的系统运行会崩溃,我看了一下,确实如此,主要是3.0之后想要获取正在运行的任务,需要加上权限声明。在AndroidManifest.xml中加入 
<uses-permission android:name="android.permission.GET_TASKS" /> 
即可解决此问题。

Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果的更多相关文章

  1. Android 桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

    首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下类似的效果. 先谈一下基本的实现原理,这种桌面悬浮窗的效果很 ...

  2. Android仿360手机卫士悬浮窗效果

    请看下图:                         首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下 ...

  3. 仿360手机卫士界面效果android版源码

    仿360手机卫士界面效果android版,这个今天一大早在源码天堂的那个网站上看到了一个那个网站最新更新的一个源码,所以就分享给大学习一下吧,布局还挺不错的,而且也很简单的,我就不把我修改的那个分享出 ...

  4. Android静默安装实现方案,仿360手机助手秒装和智能安装功能

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/47803149 之前有非常多朋友都问过我.在Android系统中如何才干实现静默安装 ...

  5. ANDROID——仿360手机卫士的旋转打分控件

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 简介 灵感源自360手机卫,主要功能就是实现显示评分或等级的效果.并稍微改良了一下,有更好 ...

  6. Android 自定义View修炼-仿360手机卫士波浪球进度的实现

    像360卫士的波浪球进度的效果,一般最常用的方法就是 画线的方式,先绘sin线或贝塞尔曲线,然后从左到右绘制竖线,然后再裁剪圆区域. 今天我这用图片bitmap的方式,大概的方法原理是: (1)首先用 ...

  7. Android 控件进阶修炼-仿360手机卫士波浪球进度控件

    技术:Android+java   概述 像360卫士的波浪球进度的效果,一般最常用的方法就是 画线的方式,先绘sin线或贝塞尔曲线,然后从左到右绘制竖线,然后再裁剪圆区域. 今天我这用图片bitma ...

  8. Android手机安全软件的恶意程序检测靠谱吗--LBE安全大师、腾讯手机管家、360手机卫士恶意软件检测方法研究

    转载请注明出处,谢谢. Android系统开放,各大论坛活跃,应用程序分发渠道广泛,这也就为恶意软件的传播提供了良好的环境.好在手机上安装了安全软件,是否能有效的检测出恶意软件呢?下边针对LBE安全大 ...

  9. 由360手机卫士谈起——让你的service获取最高权限。

    近日来,我在倒腾360手机卫士的时候,发现,你无论是把他数据清空,还是把它强行停止以后,甚至是把它卸载以后,它的service都没有被Android的系统干掉,依然是岿然不动了.我就感到了纳闷了,后来 ...

随机推荐

  1. 快速理解Java中的五种单例模式(转)

    解法一:只适合单线程环境(不好) package test; /** * @author xiaoping * */ public class Singleton { private static S ...

  2. [Nuxt] Add Arrays of Data to the Vuex Store and Display Them in Vue.js Templates

    You add array of todos to the store simply by adding them to the state defined in your store/index.j ...

  3. [Nuxt] Add CSS Libraries to Nuxt

    You can easily add CSS libraries to Nuxt using yarn or npm to install them, then simply adding them ...

  4. Perl读写Excel简单操作

    Perl读写Excel简单操作 使用模块 Spreadsheet::ParseExcel Spreadsheet::WriteExcel 读Excel #!/usr/bin/perl -w use s ...

  5. Node.js日志框架选型比較:Bunyan

    前一篇Node.js日志框架选型比較:Winston Bunyan Bunyan(by Trent Mick)是另外一个值得考虑的日志框架,以稍微不同的方式处理结构化,机器可读性被重点对待. 其结果是 ...

  6. BZOJ 1588 HNOI2002 营业额统计 裸Treap

    题目大意:...题目描写叙述不全看这里好了 给定一个序列 对于每一个元素我们定义该数的最小波动值为这个数与前面全部数的差中的最小值(第一个数的最小波动值为第一个数本身) 求最小波动值之和 找近期的数仅 ...

  7. [CSS] Manipulate Images Using CSS Filter and Blend Modes

    Apply filters like blur, brightness, saturation and hue to images. Combined with CSS blend modes, yo ...

  8. Qt元类型(MetaType)注册门(使用qRegisterMetaType注册错误等级)

    昨天调试项目时,突然发现如下消息: QObject::connect: Cannot queue arguments of type 'ERROR_LEVEL' (Make sure 'ERROR_L ...

  9. html5-5 HTML5表单元素和内嵌框架

    html5-5   HTML5表单元素和内嵌框架 一.总结 一句话总结:单选框和多选框选的时候外面加label就可以实现选后面文字也可以选中了 1.html5如何实现文件上传? 必须加上enctype ...

  10. System.Xml.XmlException: 引用了未声明的实体“nbsp”

    在XML文件中<, >,&等有特殊含义,(前两个字符用于链接签,&用于转义),不能直接使用.使用这些个字符时,应使用它们的转义序列,下面是5个在XML文件中预定义好的实体: ...