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

通过WindowManager的addView()方法,并设置WindowManager.LayoutParams的相关属性,就可以往WindowManager中加入所需要的View,而根据WindowManager.LayoutParams属性不同,也就能实现不同的效果。比如创建系统顶级窗口,实现悬浮窗口效果。如果需要将View从WindowManager中移除,只需要调用removeView()即可。

1、代码实现主界面为一个Button按钮点击跳转到小悬浮窗口,然后关闭本窗口。

  1. package com.example.suspend;
  2.  
  3. import android.os.Bundle;
  4. import android.app.Activity;
  5. import android.content.Intent;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.Button;
  9. import android.widget.TextView;
  10.  
  11. public class MainActivity extends Activity {
  12. private Button suspend;
  13. private TextView text;
  14.  
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.main);
  19. initUI();
  20. }
  21.  
  22. private void initUI() {
  23. // TODO Auto-generated method stub
  24. // WindowService wind = new WindowService();
  25. suspend = (Button)findViewById(R.id.suspend);
  26. suspend.setOnClickListener(new suspendListener());
  27. text = (TextView)findViewById(R.id.text);
  28. text.setText(MyWindowManager.getUsedPercentValue(getApplicationContext()));
  29. }
  30. public class suspendListener implements OnClickListener{
  31.  
  32. @Override
  33. public void onClick(View arg0) {
  34. // TODO Auto-generated method stub
  35. //启动悬浮窗口关闭本窗口
  36. Intent intent = new Intent(MainActivity.this,WindowService.class);
  37. startService(intent);
  38. finish();
  39. }
  40. }
  41.  
  42. }

2、WindowService 中使用了一个定时器,定时为500ms,在定时器里创建小窗口,在启动前先判断是否在桌面

  1. /**
  2. * 判断当前界面是否桌面
  3. */
  4. private boolean isHome(){
  5. ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
  6. List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks();
  7. return getHomes().contains(rti.get().topActivity.getPackageName());
  8. }
  9.  
  10. /**
  11. * 获得属于桌面的应用的应用包名称
  12. * @return 返回包含所有包名的字符串列表
  13. */
  14. private List<String> getHomes(){
  15. List<String> names = new ArrayList<String>();
  16. PackageManager packageManager = this.getPackageManager();
  17. Intent intent = new Intent(Intent.ACTION_MAIN);
  18. intent.addCategory(Intent.CATEGORY_HOME);
  19. List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
  20. for (ResolveInfo ri : resolveInfo){
  21. names.add(ri.activityInfo.packageName);
  22. System.out.println("packageName" + names);
  23. Log.d(TAG,"tag:"+names);
  24. }
  25. return names;
  26. }

完整代码为:

  1. package com.example.suspend;
  2.  
  3. import android.app.ActivityManager;
  4. import android.app.Service;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.pm.PackageManager;
  8. import android.content.pm.ResolveInfo;
  9. import android.os.Handler;
  10. import android.os.IBinder;
  11. import android.util.Log;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.Timer;
  15. import java.util.TimerTask;
  16.  
  17. public class WindowService extends Service {
  18. private static final String TAG = "PACKAGENAME";
  19. //用于线程中创建或移除悬浮窗。
  20. private Handler handler = new Handler();
  21. //定时器,定时进行检测当前应该创建还是移除悬浮
  22. private Timer timer;
  23. @Override
  24. public IBinder onBind(Intent intent) {
  25. // TODO: Return the communication channel to the service.
  26. return null;
  27. }
  28.  
  29. /**
  30. * 启动service的时候,onCreate方法只有第一次会调用,onStartCommand和onStart每次都被调用。
  31. * onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止
  32. * 这个整形可以有四个返回值:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。
  33. */
  34. @Override
  35. public int onStartCommand(Intent intent, int flags, int startId) {
  36. /**
  37. * 开启定时器,每隔500ms刷新一次
  38. */
  39. if (timer == null){
  40. timer = new Timer();
  41. timer.scheduleAtFixedRate(new RefreshTask(),,);
  42. }
  43. return super.onStartCommand(intent, flags, startId);
  44. }
  45.  
  46. @Override
  47. public void onDestroy() {
  48. super.onDestroy();
  49. //Service 被终止的同时也停止定时器继续运行
  50. timer.cancel();
  51. timer = null;
  52. }
  53.  
  54. class RefreshTask extends TimerTask{
  55.  
  56. @Override
  57. public void run() {
  58. //判断当前界面是桌面,且没有悬浮显示,则创建悬浮窗
  59. if (isHome() && !MyWindowManager.isWindowShowing()){
  60. handler.post(new Runnable() {
  61. @Override
  62. public void run() {
  63. MyWindowManager.createSmallWindow(getApplicationContext());
  64. }
  65. });
  66. }
  67. //当前界面不是桌面,且有悬浮窗口显示,则移除悬浮窗口
  68. else if (!isHome() && MyWindowManager.isWindowShowing()){
  69. handler.post(new Runnable() {
  70. @Override
  71. public void run() {
  72. MyWindowManager.removeSmallWindow(getApplicationContext());
  73. MyWindowManager.removeBigWindow(getApplicationContext());
  74. }
  75. }) ;
  76. }
  77. // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。
  78. else if (isHome() && MyWindowManager.isWindowShowing()){
  79. handler.post(new Runnable() {
  80. @Override
  81. public void run() {
  82. MyWindowManager.updateUsedPercent(getApplicationContext());
  83. }
  84. });
  85. }
  86. }
  87. }
  88.  
  89. /**
  90. * 判断当前界面是否桌面
  91. */
  92. private boolean isHome(){
  93. ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
  94. List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks();
  95. return getHomes().contains(rti.get().topActivity.getPackageName());
  96. }
  97.  
  98. /**
  99. * 获得属于桌面的应用的应用包名称
  100. * @return 返回包含所有包名的字符串列表
  101. */
  102. private List<String> getHomes(){
  103. List<String> names = new ArrayList<String>();
  104. PackageManager packageManager = this.getPackageManager();
  105. Intent intent = new Intent(Intent.ACTION_MAIN);
  106. intent.addCategory(Intent.CATEGORY_HOME);
  107. List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
  108. for (ResolveInfo ri : resolveInfo){
  109. names.add(ri.activityInfo.packageName);
  110. System.out.println("packageName" + names);
  111. Log.d(TAG,"tag:"+names);
  112. }
  113. return names;
  114. }
  115. }

3、创建窗口方法具体代码有写

  1. public static void createSmallWindow(Context context){
  2. //WindowManager基本用到:addView,removeView,updateViewLayout
  3. WindowManager windowManager = getWindowManager(context);
  4. //获取屏幕宽高 abstract Display getDefaultDisplay(); //获取默认显示的 Display 对象
  5. int screenWidth = windowManager.getDefaultDisplay().getWidth();
  6. int screenHeight = windowManager.getDefaultDisplay().getHeight();
  7.  
  8. //设置小悬浮窗口的位置以及相关参数
  9. if (smallWindowActivity == null) {
  10. smallWindowActivity = new SmallWindowActivity(context);
  11. if (smallWindowParams == null) {
  12. smallWindowParams = new LayoutParams();//
  13. smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
  14. smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
  15. smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
  16. | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
  17. smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
  18. smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
  19. smallWindowParams.height = SmallWindowActivity.viewHeight;
  20. smallWindowParams.x = screenWidth;//设置悬浮窗口位置
  21. smallWindowParams.y = screenHeight / ;
  22. }
  23. smallWindowActivity.setParams(smallWindowParams);
  24. windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
  25. }
  26. }

要移除窗口可使用 windowManager.removeView(smallWindowActivity);

小窗口实现的是显示手机内存百分比,下面为计算内存百分比的方法:

  1. /**
  2. * 计算已使用内存的百分比,并返回。
  3. *
  4. * @param context
  5. * 可传入应用程序上下文。
  6. * @return 已使用内存的百分比,以字符串形式返回。
  7. */
  8. public static String getUsedPercentValue(Context context) {
  9. String dir = "/proc/meminfo";
  10. try {
  11. FileReader fr = new FileReader(dir);
  12. BufferedReader br = new BufferedReader(fr, );
  13. String memoryLine = br.readLine();
  14. String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
  15. br.close();
  16. long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
  17. long availableSize = getAvailableMemory(context) / ;
  18. int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * );
  19. return percent + "%";
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. return "悬浮窗";
  24. }
  25.  
  26. /**
  27. * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
  28. *
  29. * @param context
  30. * 可传入应用程序上下文。
  31. */
  32. public static void updateUsedPercent(Context context) {
  33. if (smallWindowActivity != null) {
  34. TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
  35. percentView.setText(getUsedPercentValue(context));
  36. }
  37. }
  38. /**
  39. * 获取当前可用内存,返回数据以字节为单位。
  40. *
  41. * @param context
  42. * 可传入应用程序上下文。
  43. * @return 当前可用内存。
  44. */
  45. private static long getAvailableMemory(Context context) {
  46. ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
  47. getActivityManager(context).getMemoryInfo(mi);
  48. return mi.availMem;
  49. }
  50.  
  51. /**
  52. * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。
  53. *
  54. * @param context
  55. * 可传入应用程序上下文。
  56. * @return ActivityManager的实例,用于获取手机可用内存。
  57. */
  58. private static ActivityManager getActivityManager(Context context) {
  59. if (mactivityManager == null) {
  60. mactivityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
  61. }
  62. return mactivityManager;
  63. }

完整的MyWindowManager类代码为

  1. package com.example.suspend;
  2.  
  3. import android.app.ActivityManager;
  4. import android.content.Context;
  5. import android.graphics.PixelFormat;
  6. import android.view.Gravity;
  7. import android.view.WindowManager.LayoutParams;
  8. import android.view.WindowManager;
  9. import android.widget.TextView;
  10.  
  11. import java.io.BufferedReader;
  12. import java.io.FileReader;
  13. import java.io.IOException;
  14.  
  15. public class MyWindowManager{
  16. //小悬浮窗View的实例
  17. private static SmallWindowActivity smallWindowActivity;
  18.  
  19. //大悬浮窗View的实例
  20. private static BigWindowActivity bigWindowActivity;
  21.  
  22. //小悬浮View的参数
  23. private static LayoutParams smallWindowParams;
  24.  
  25. //大悬浮View的参数
  26. private static LayoutParams bigWindowParams;
  27.  
  28. //用于控制在屏幕上添加或移除悬浮窗
  29. private static WindowManager mWindowManager;
  30.  
  31. //用于获取手机可用内存
  32. private static ActivityManager mactivityManager;
  33.  
  34. public static void createSmallWindow(Context context){
  35. //WindowManager基本用到:addView,removeView,updateViewLayout
  36. WindowManager windowManager = getWindowManager(context);
  37. //获取屏幕宽高 abstract Display getDefaultDisplay(); //获取默认显示的 Display 对象
  38. int screenWidth = windowManager.getDefaultDisplay().getWidth();
  39. int screenHeight = windowManager.getDefaultDisplay().getHeight();
  40.  
  41. //设置小悬浮窗口的位置以及相关参数
  42. if (smallWindowActivity == null) {
  43. smallWindowActivity = new SmallWindowActivity(context);
  44. if (smallWindowParams == null) {
  45. smallWindowParams = new LayoutParams();//
  46. smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
  47. smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
  48. smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
  49. | LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
  50. smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
  51. smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
  52. smallWindowParams.height = SmallWindowActivity.viewHeight;
  53. smallWindowParams.x = screenWidth;//设置悬浮窗口位置
  54. smallWindowParams.y = screenHeight / ;
  55. }
  56. smallWindowActivity.setParams(smallWindowParams);
  57. windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
  58. }
  59. }
  60.  
  61. /**
  62. * 创建一个大悬浮窗。位置为屏幕正中间。
  63. *
  64. * @param context
  65. * 必须为应用程序的Context.
  66. */
  67. // @SuppressWarnings("deprecation")
  68. public static void createBigWindow(Context context) {
  69. WindowManager windowManager = getWindowManager(context);
  70. int screenWidth = windowManager.getDefaultDisplay().getWidth();
  71. int screenHeight = windowManager.getDefaultDisplay().getHeight();
  72. if (bigWindowActivity == null) {
  73. bigWindowActivity = new BigWindowActivity(context);
  74. if (bigWindowParams == null) {
  75. bigWindowParams = new LayoutParams();
  76. bigWindowParams.x = screenWidth / - BigWindowActivity.viewWidth / ;
  77. bigWindowParams.y = screenHeight / - BigWindowActivity.viewHeight / ;
  78. bigWindowParams.type = LayoutParams.TYPE_PHONE;
  79. bigWindowParams.format = PixelFormat.RGBA_8888;
  80. bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
  81. bigWindowParams.width = BigWindowActivity.viewWidth;
  82. bigWindowParams.height = BigWindowActivity.viewHeight;
  83. }
  84. windowManager.addView(bigWindowActivity, bigWindowParams);
  85. }
  86. }
  87.  
  88. /**
  89. * 将小悬浮窗从屏幕上移除。
  90. * abstract void removeViewImmediate(View view);//是removeView(View) 的一个特殊扩展,
  91. * 在方法返回前能够立即调用该视图层次的View.onDetachedFromWindow() 方法。
  92. * @param context
  93. * 必须为应用程序的Context.
  94. */
  95. public static void removeSmallWindow(Context context) {
  96. if (smallWindowActivity != null) {
  97. WindowManager windowManager = getWindowManager(context);
  98. windowManager.removeView(smallWindowActivity);//移除悬浮窗口
  99. smallWindowActivity = null;
  100. }
  101. }
  102.  
  103. /**
  104. * 将大悬浮窗从屏幕上移除。
  105. *
  106. * @param context
  107. * 必须为应用程序的Context.
  108. */
  109. public static void removeBigWindow(Context context) {
  110. if (bigWindowActivity != null) {
  111. WindowManager windowManager = getWindowManager(context);
  112. windowManager.removeView(bigWindowActivity);
  113. bigWindowActivity = null;
  114. }
  115. }
  116.  
  117. /**
  118. * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
  119. *
  120. * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
  121. */
  122. public static boolean isWindowShowing() {
  123. return smallWindowActivity != null || bigWindowActivity != null;
  124. //return smallWindowActivity != null;
  125. }
  126.  
  127. /**
  128. * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
  129. *
  130. * @param context
  131. * 必须为应用程序的Context.
  132. * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
  133. */
  134. private static WindowManager getWindowManager(Context context) {
  135. if (mWindowManager == null) {
  136. mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  137. }
  138. return mWindowManager;
  139. }
  140.  
  141. /**
  142. * 计算已使用内存的百分比,并返回。
  143. *
  144. * @param context
  145. * 可传入应用程序上下文。
  146. * @return 已使用内存的百分比,以字符串形式返回。
  147. */
  148. public static String getUsedPercentValue(Context context) {
  149. String dir = "/proc/meminfo";
  150. try {
  151. FileReader fr = new FileReader(dir);
  152. BufferedReader br = new BufferedReader(fr, );
  153. String memoryLine = br.readLine();
  154. String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
  155. br.close();
  156. long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
  157. long availableSize = getAvailableMemory(context) / ;
  158. int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * );
  159. return percent + "%";
  160. } catch (IOException e) {
  161. e.printStackTrace();
  162. }
  163. return "悬浮窗";
  164. }
  165.  
  166. /**
  167. * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
  168. *
  169. * @param context
  170. * 可传入应用程序上下文。
  171. */
  172. public static void updateUsedPercent(Context context) {
  173. if (smallWindowActivity != null) {
  174. TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
  175. percentView.setText(getUsedPercentValue(context));
  176. }
  177. }
  178. /**
  179. * 获取当前可用内存,返回数据以字节为单位。
  180. *
  181. * @param context
  182. * 可传入应用程序上下文。
  183. * @return 当前可用内存。
  184. */
  185. private static long getAvailableMemory(Context context) {
  186. ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
  187. getActivityManager(context).getMemoryInfo(mi);
  188. return mi.availMem;
  189. }
  190.  
  191. /**
  192. * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。
  193. *
  194. * @param context
  195. * 可传入应用程序上下文。
  196. * @return ActivityManager的实例,用于获取手机可用内存。
  197. */
  198. private static ActivityManager getActivityManager(Context context) {
  199. if (mactivityManager == null) {
  200. mactivityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
  201. }
  202. return mactivityManager;
  203. }
  204.  
  205. }

4、小窗口的代码继承了LinearLayout,可实现手动触摸移动,分别设置了手指点击下、移动和离开的处理。实现代码为:

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. switch (event.getAction()){
  4. case MotionEvent.ACTION_DOWN:
  5. //手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
  6. xInView = event.getX();
  7. yInView = event.getY();
  8. xDownInScreen = event.getRawX();
  9. yDownInScreen = event.getRawY() - getStatusBarHeight();
  10. xInScreen = event.getRawX();
  11. yInScreen = event.getRawY() - getStatusBarHeight();
  12. break;
  13. case MotionEvent.ACTION_MOVE:
  14. xInScreen = event.getRawX();
  15. yInScreen = event.getRawY()-getStatusBarHeight();
  16. //手指一动的时候就更新悬浮窗位置
  17. updateViewPosition();
  18. break;
  19. case MotionEvent.ACTION_UP:
  20. //如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen == yInScreen
  21. //则视为触发
  22. if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
  23. MyWindowManager.createBigWindow(getContext());//创建大窗口
  24. MyWindowManager.removeSmallWindow(getContext());//移除小窗口
  25. Toast.makeText(getContext(), "手指离开屏幕!", Toast.LENGTH_SHORT).show();
  26. }
  27. break;
  28. default:
  29. break;
  30. }
  31. return true;
  32. }

完整代码:

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

小窗口仅实现了显示内存百分比,添加了一个TextView,小窗口的布局代码为:

  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>

5、当点击小窗口的时候添加了监听就是当手指离开的时候启动创建一个另一个窗口然后关闭本窗口。

大窗口也是实现点击可以随意移动,还添加了两个按钮的功能,发送短信和返回。实现代码为:

  1. package com.example.suspend;
  2.  
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.util.DisplayMetrics;
  6. import android.util.Log;
  7. import android.view.LayoutInflater;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.widget.Button;
  11. import android.widget.LinearLayout;
  12.  
  13. public class BigWindowActivity extends LinearLayout {
  14.  
  15. //记录大悬浮窗的宽度
  16. public static int viewWidth;
  17.  
  18. //记录大悬浮窗高度
  19. public static int viewHeight;
  20.  
  21. private Button phone,sms,app,music;
  22. int screenWidth,screenHeight;
  23. int lastX,lastY;//记录移动的最后的位置
  24. int dx,dy;
  25.  
  26. public BigWindowActivity(final Context context) {
  27. super(context);
  28. LayoutInflater.from(context).inflate(R.layout.big_window, this);
  29. View view = findViewById(R.id.big_window_layout);
  30. viewWidth = view.getLayoutParams().width;
  31. viewHeight = view.getLayoutParams().height;
  32.  
  33. initUI();
  34.  
  35. }
  36.  
  37. private void initUI() {
  38. // TODO Auto-generated method stub
  39. //获取屏幕的分辨率
  40. DisplayMetrics dm = getResources().getDisplayMetrics();
  41. screenWidth = dm.widthPixels;
  42. screenHeight = dm.heightPixels-;
  43. Button back = (Button)findViewById(R.id.fanhui);
  44. //添加触摸监听
  45. back.setOnTouchListener(new OnTouchListener() {
  46.  
  47. @Override
  48. public boolean onTouch(View v, MotionEvent event) {
  49. // TODO Auto-generated method stub
  50. //获取Action
  51. int ea = event.getAction();
  52. Log.i("TAG","Touch:"+ea);
  53. switch (ea){
  54. case MotionEvent.ACTION_DOWN:
  55. lastX = (int)event.getRawX();
  56. lastY = (int)event.getRawY();
  57. break;
  58. case MotionEvent.ACTION_MOVE:
  59. //移动中动态设置位置
  60. mobilesetting(v,event);
  61. break;
  62. case MotionEvent.ACTION_UP://当手指离开的时候执行
  63. if (dx==dy){
  64. comeback(getContext());
  65. }
  66. break;
  67. }
  68. return false;
  69. }
  70. });
  71.  
  72. sms = (Button)findViewById(R.id.SMS);
  73. sms.setOnTouchListener(new OnTouchListener() {
  74.  
  75. @Override
  76. public boolean onTouch(View v, MotionEvent event) {
  77. // TODO Auto-generated method stub
  78. int ea = event.getAction();
  79. Log.i("TAG","Touch:"+ea);
  80. switch (ea){
  81. case MotionEvent.ACTION_DOWN:
  82. lastX = (int)event.getRawX();
  83. lastY = (int)event.getRawY();
  84. break;
  85. case MotionEvent.ACTION_MOVE:
  86. //移动中动态设置位置
  87. mobilesetting(v,event);
  88. break;
  89. case MotionEvent.ACTION_UP://当手指离开的时候执行
  90. if (dx==dy){
  91. sms();
  92. }
  93. break;
  94. }
  95.  
  96. return false;
  97. }
  98. });
  99. }
  100. /**
  101. * 点击返回移除大窗口创建小窗口
  102. * @param context
  103. */
  104. public void comeback(Context context){
  105. // 点击返回的时候,移除大悬浮窗,创建小悬浮窗
  106. MyWindowManager.removeBigWindow(context);
  107. MyWindowManager.createSmallWindow(context);
  108. }
  109. //发短信
  110. public void sms(){
  111. Intent it = new Intent(Intent.ACTION_VIEW);
  112. it.putExtra("sms_body", "The SMS text");
  113. it.setType("vnd.android-dir/mms-sms");
  114. it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  115. getContext().startActivity(it);
  116. comeback(getContext());
  117. }
  118.  
  119. /**
  120. * 移动控件设置位置
  121. * @param v
  122. * @param event
  123. */
  124. public void mobilesetting(View v,MotionEvent event){
  125. //移动中动态设置位置
  126. dx = (int)event.getRawX()-lastX;//移动中x当前位置
  127. dy = (int)event.getRawY()-lastY;
  128.  
  129. int left = v.getLeft()+dx;
  130. int top = v.getTop()+dy;
  131. int right = v.getRight()+dx;
  132. int bottom = v.getBottom()+dy;
  133.  
  134. if(left<){
  135. left=;
  136. right = left+v.getWidth();//
  137. }
  138. if (right>screenWidth){
  139. right = screenWidth;
  140. left = right - v.getWidth();//max
  141. }
  142. if(top < ){
  143. top = ;
  144. bottom = top + v.getHeight();
  145. }
  146. if(bottom > screenHeight){
  147. bottom = screenHeight;
  148. top = bottom - v.getHeight();
  149. }
  150. v.layout(left, top, right, bottom);
  151. //将当前的位置再次设置
  152. lastX = (int) event.getRawX();
  153. lastY = (int) event.getRawY();
  154. }
  155. // @Override
  156. // public boolean onTouchEvent(MotionEvent event) {
  157. // // TODO Auto-generated method stub
  158. // //点击屏幕弹出的框会消失
  159. // //popupWindow.dismiss();
  160. // return super.onTouchEvent(event);
  161. // }
  162. }

布局代码为四个按钮控件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:id="@+id/big_window_layout"
  6. >
  7.  
  8. <Button
  9. android:layout_marginTop="200dip"
  10. android:layout_marginLeft="150dip"
  11. android:id="@+id/fanhui"
  12. android:layout_width="@dimen/bigwidth"
  13. android:layout_height="@dimen/bigheight"
  14. android:textColor="#ffffff"
  15. android:textSize="@dimen/bigtextsize"
  16. android:background="@xml/shape"
  17. android:text="@string/rt"
  18. />
  19.  
  20. <Button
  21. android:id="@+id/app"
  22. android:layout_width="@dimen/bigwidth"
  23. android:layout_height="@dimen/bigheight"
  24. android:layout_above="@+id/music"
  25. android:layout_alignLeft="@+id/fanhui"
  26. android:background="@xml/shape"
  27. android:textSize="@dimen/bigtextsize"
  28. android:text="@string/appone" />
  29.  
  30. <Button
  31. android:id="@+id/music"
  32. android:layout_width="@dimen/bigwidth"
  33. android:layout_height="@dimen/bigheight"
  34. android:layout_alignBaseline="@+id/fanhui"
  35. android:layout_alignBottom="@+id/fanhui"
  36. android:layout_toLeftOf="@+id/app"
  37. android:textSize="@dimen/bigtextsize"
  38. android:text="@string/mu"
  39. android:background="@xml/shape"
  40. />
  41.  
  42. <Button
  43. android:id="@+id/SMS"
  44. android:layout_width="@dimen/bigwidth"
  45. android:layout_height="@dimen/bigheight"
  46. android:layout_below="@+id/fanhui"
  47. android:layout_toRightOf="@+id/music"
  48. android:textSize="@dimen/bigtextsize"
  49. android:background="@xml/shape"
  50. android:text="@string/sendsms" />
  51.  
  52. <Button
  53. android:id="@+id/Phone"
  54. android:layout_width="@dimen/bigwidth"
  55. android:layout_height="@dimen/bigheight"
  56. android:layout_alignBaseline="@+id/fanhui"
  57. android:layout_alignBottom="@+id/fanhui"
  58. android:textSize="@dimen/bigtextsize"
  59. android:layout_toRightOf="@+id/fanhui"
  60. android:background="@xml/shape"
  61. android:text="@string/callphone" />
  62.  
  63. </RelativeLayout>

控件的实现效果是在xml里面添加了shape.xml,设置了控件的颜色跟形状。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:shape="oval">
  4. <!-- 实心 -->
  5. <solid android:color="#ff9d77"/>
  6. <!-- 渐变 -->
  7. <gradient
  8. android:startColor="#ff8c00"
  9. android:endColor="#FFFFFF"
  10. android:angle="" />
  11. <!-- 描边 -->
  12. <stroke
  13. android:width="2dp"
  14. android:color="#dcdcdc" />
  15. <!-- 圆角 -->
  16. <corners
  17. android:radius="2dp" />
  18. <padding
  19. android:left="10dp"
  20. android:top="10dp"
  21. android:right="10dp"
  22. android:bottom="10dp" />
  23. </shape>

6、使用到的权限为:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.suspend"
  4. android:versionCode=""
  5. android:versionName="1.0" >
  6.  
  7. <uses-sdk
  8. android:minSdkVersion=""
  9. android:targetSdkVersion="" />
  10. <!-- 添加悬浮窗口权限 -->
  11. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  12. <uses-permission android:name="android.permission.INTERNET"/>
  13. <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  14. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  15. <application
  16. android:allowBackup="true"
  17. android:icon="@drawable/ic_launcher"
  18. android:label="@string/app_name"
  19. android:theme="@style/AppTheme" >
  20. <activity
  21. android:name="com.example.suspend.MainActivity"
  22. android:label="@string/app_name" >
  23. <intent-filter>
  24. <action android:name="android.intent.action.MAIN" />
  25.  
  26. <category android:name="android.intent.category.LAUNCHER" />
  27. </intent-filter>
  28. </activity>
  29. <service android:name=".WindowService"></service>
  30. </application>
  31.  
  32. </manifest>

实现的效果图:


代码下载地址:点击打开链接

Android 类似360悬浮窗口实现源码的更多相关文章

  1. 50个Android开发人员必备UI效果源码[转载]

    50个Android开发人员必备UI效果源码[转载] http://blog.csdn.net/qq1059458376/article/details/8145497 Android 仿微信之主页面 ...

  2. [转载] 50个Android开发人员必备UI效果源码

    好东西,多学习! Android 仿微信之主页面实现篇Android 仿微信之界面导航篇Android 高仿QQ 好友分组列表Android 高仿QQ 界面滑动效果Android 高仿QQ 登陆界面A ...

  3. 编译Android 4.4.4 r1的源码刷Nexus 5手机详细教程

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/54562606 网上关于编译Android源码的教程已经很多了,但是讲怎么编译And ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. Android版的菜谱客户端应用源码完整版

    Android版的菜谱客户端应用源码完整版,这个文章是从安卓教程网转载过来的,不是本人的原创,希望能够帮到大家的学习吧. <ignore_js_op> 152936qc7jdnv6vo0c ...

  8. android浪漫樱花凋零动态壁纸应用源码

    android浪漫樱花凋零动态壁纸应用源码,是从那个安卓教程网拿过来的,本项目是一套基于安卓的樱花动态壁纸项目源码,安装以后桌面没有图标,但是可以在修改壁纸-动态壁纸中找到.我的分辨率是480×854 ...

  9. Android进阶:五、RxJava2源码解析 2

    上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...

随机推荐

  1. Entity Framework Code-First(3):Setup Environment

    Setup Development Environment for EF Code-First: Let's setup the development environment for Code-Fi ...

  2. Spring入门第三课

    属性注入 属性注入就是通过setter方法注入Bean的属性值或依赖的对象. 属性植入使用<property>元素,使用name属性指定Bean的属性名称,value属性或者<val ...

  3. bash字符串匹配

    #!/bin/shfoo(){    local basedir=$1    local all_entries=`ls -c`    for entry in $all_entries    do ...

  4. SpringBoot应用篇(一):自定义starter

    一.码前必备知识 1.SpringBoot starter机制 SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在mave ...

  5. vue中通过cross-env插件配置三种环境(开发,测试,生产)打包,不用切换api

    1. 话不多说,第一步就是安装必要的插件 npm install cross-env --save 2.修改config里面的参数,这里只展示一个test,其他类似 3.修改package.json ...

  6. 初识SVN

    前言 我们都知道每一件工具的诞生都是为了方便我们的生活.SVN(Subversion)学习工具在我们"合作"开发软件过程中起到了很大的作用.说起SVN先说说SCM. 内容 SCM ...

  7. 在Python中使用asyncio进行异步编程

    对于来自JavaScript编码者来说,异步编程不是什么新东西,但对于Python开发者来说,async函数和future(类似JS的promise)可不是那么容易能理解的. Concurrency ...

  8. SharePoint2016 母版页引用样式和脚本路径无效

    直接引用16目录(/_layouts/16/)会导致页面找不到文件,必须将16目录改为15目录(/_layouts/15/),估计是内部机制还没有更新,这个坑不知道要多久才会填上=,=

  9. 简述raid0,raid1,raid5,raid10 的工作原理及特点

    RAID 0 支持1块盘到多块盘,容量是所有盘之和 RAID1 只支持2块盘,容量损失一块盘 RAID 5最少三块盘,不管硬盘数量多少,只损失一块容量 RAID 10最少4块盘,必须偶数硬盘,不管硬盘 ...

  10. spring框架详细课程视频

    https://ke.qq.com/course/27346#term_id=100012852