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


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

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

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

    在FloatWindowSmallView中加入如下代码:

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

    在FloatWindowBigView中加入如下代码:

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

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

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

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

    [html] view plaincopy
    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.   
    7.     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  
    8.   
    9.     <uses-sdk  
    10.         android:minSdkVersion="8"  
    11.         android:targetSdkVersion="8" />  
    12.   
    13.     <application  
    14.         android:allowBackup="true"  
    15.         android:icon="@drawable/ic_launcher"  
    16.         android:label="@string/app_name"  
    17.         android:theme="@style/AppTheme" >  
    18.         <activity  
    19.             android:name="com.demo.floatwindowdemo.MainActivity"  
    20.             android:label="@string/app_name" >  
    21.             <intent-filter>  
    22.                 <action android:name="android.intent.action.MAIN" />  
    23.   
    24.                 <category android:name="android.intent.category.LAUNCHER" />  
    25.             </intent-filter>  
    26.         </activity>  
    27.           
    28.         <service android:name=".FloatWindowService"></service>  
    29.     </application>  
    30.   
    31. </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" /> 
    即可解决此问题。
  • 相关阅读:
    python基础练习题(题目 学习使用auto定义变量的用法)
    python基础练习题(题目 模仿静态变量的用法)
    roaring bitmap 与 bitmap 比较. 编译运行
    Linux 实现开关机测试,记录开机次数
    Linux 显示开机欢迎信息(/etc/issue 与 /etc/motd)
    Linux 修改文件权限概述
    把编译好的程序放在Linux系统里,实现不需要配置程序和动态库的环境变量,直接执行
    Linux 提示 is not a symbolic link 错误解决方法
    Ubuntu 自动更新详解【转】
    C#线程入门
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6470089.html
Copyright © 2020-2023  润新知