Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果&Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现

Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果 立即下载

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

出处:http://blog.youkuaiyun.com/guolin_blog/article/details/8689140

大家好,今天给大家带来一个仿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.      */  
  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(), 0, 500);  
  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 plain copy
  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 plain copy
  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 plain copy
  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 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.   
  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桌面悬浮窗进阶,QQ手机管家小火箭效果实现 。

源码下载,请点击这里

补充:

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

出处:http://blog.youkuaiyun.com/guolin_blog/article/details/16919859

今天是2013年的最后一天了,这里首先提前祝大家新年快乐!同时,本篇文章也是我今年的最后一篇文章了,因此我想要让它尽量有点特殊性,比起平时的文章要多一些特色。记得在今年年初的时候,我写的第一篇文章是模仿360手机卫士的桌面悬浮窗效果,那么为了能够首尾呼应,今年的最后一篇文章就同样还是来实现桌面悬浮窗的效果吧,当然效果将会更加高级。

相信用过QQ手机管家的朋友们都会知道它有一个小火箭加速的功能,将小火箭拖动到火箭发射台上发射就会出现一个火箭升空的动画,那么今天我们就来模仿着实现一下这个效果吧。

这次我们将代码的重点放在火箭升空的效果上,因此简单起见,就直接在模仿360手机卫士悬浮窗的那份代码的基础上继续开发了,如果你还没有看过那篇文章的话,建议先去阅读 Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果 。

比起普通的桌面悬浮窗,现在我们需要在拖动悬浮窗的时候将悬浮窗变成一个小火箭,并且在屏幕的底部添加一个火箭发射台。那么我们就从火箭发射台开始编写吧,首先创建launcher.xml作为火箭发射台的布局文件,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="wrap_content"  
  5.     android:layout_height="wrap_content"  
  6.     android:orientation="vertical"  
  7.     >  
  8.       
  9.     <ImageView   
  10.         android:id="@+id/launcher_img"  
  11.         android:layout_width="200dp"  
  12.         android:layout_height="88dp"  
  13.         android:src="@drawable/launcher_bg_hold"  
  14.         />  
  15.       
  16. </LinearLayout>  
可以看到,这里的ImageView是用于显示当前火箭发射台状态的。我事先准备好了两张图片,一张是当小火箭未拖动到火箭发射台时显示的,一张是当小火箭拖动到火箭发射台上时显示的。

接下来创建RocketLauncher类,作为火箭发射台的View,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class RocketLauncher extends LinearLayout {  
  2.   
  3.     /** 
  4.      * 记录火箭发射台的宽度 
  5.      */  
  6.     public static int width;  
  7.   
  8.     /** 
  9.      * 记录火箭发射台的高度 
  10.      */  
  11.     public static int height;  
  12.   
  13.     /** 
  14.      * 火箭发射台的背景图片 
  15.      */  
  16.     private ImageView launcherImg;  
  17.   
  18.     public RocketLauncher(Context context) {  
  19.         super(context);  
  20.         LayoutInflater.from(context).inflate(R.layout.launcher, this);  
  21.         launcherImg = (ImageView) findViewById(R.id.launcher_img);  
  22.         width = launcherImg.getLayoutParams().width;  
  23.         height = launcherImg.getLayoutParams().height;  
  24.     }  
  25.   
  26.     /** 
  27.      * 更新火箭发射台的显示状态。如果小火箭被拖到火箭发射台上,就显示发射。 
  28.      */  
  29.     public void updateLauncherStatus(boolean isReadyToLaunch) {  
  30.         if (isReadyToLaunch) {  
  31.             launcherImg.setImageResource(R.drawable.launcher_bg_fire);  
  32.         } else {  
  33.             launcherImg.setImageResource(R.drawable.launcher_bg_hold);  
  34.         }  
  35.     }  
  36.   
  37. }  
RocketLauncher中的代码还是非常简单的,在构建方法中调用了LayoutInflater的inflate()方法来将launcher.xml这个布局文件加载进来,并获取到了当前View的宽度和高度。在updateLauncherStatus()方法中会进行判断,如果传入的参数是true,就显示小火箭即将发射的图片,如果传入的是false,就显示将小火箭拖动到发射台的图片。

新增的文件只有这两个,剩下的就是要修改之前的代码了。首先修改MyWindowManager中的代码,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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.      * 火箭发射台的实例 
  15.      */  
  16.     private static RocketLauncher rocketLauncher;  
  17.   
  18.     /** 
  19.      * 小悬浮窗View的参数 
  20.      */  
  21.     private static LayoutParams smallWindowParams;  
  22.   
  23.     /** 
  24.      * 大悬浮窗View的参数 
  25.      */  
  26.     private static LayoutParams bigWindowParams;  
  27.   
  28.     /** 
  29.      * 火箭发射台的参数 
  30.      */  
  31.     private static LayoutParams launcherParams;  
  32.   
  33.     /** 
  34.      * 用于控制在屏幕上添加或移除悬浮窗 
  35.      */  
  36.     private static WindowManager mWindowManager;  
  37.   
  38.     /** 
  39.      * 用于获取手机可用内存 
  40.      */  
  41.     private static ActivityManager mActivityManager;  
  42.   
  43.     /** 
  44.      * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。 
  45.      */  
  46.     public static void createSmallWindow(Context context) {  
  47.         WindowManager windowManager = getWindowManager(context);  
  48.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  49.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  50.         if (smallWindow == null) {  
  51.             smallWindow = new FloatWindowSmallView(context);  
  52.             if (smallWindowParams == null) {  
  53.                 smallWindowParams = new LayoutParams();  
  54.                 smallWindowParams.type = LayoutParams.TYPE_SYSTEM_ALERT;  
  55.                 smallWindowParams.format = PixelFormat.RGBA_8888;  
  56.                 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL  
  57.                         | LayoutParams.FLAG_NOT_FOCUSABLE;  
  58.                 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
  59.                 smallWindowParams.width = FloatWindowSmallView.windowViewWidth;  
  60.                 smallWindowParams.height = FloatWindowSmallView.windowViewHeight;  
  61.                 smallWindowParams.x = screenWidth;  
  62.                 smallWindowParams.y = screenHeight / 2;  
  63.             }  
  64.             smallWindow.setParams(smallWindowParams);  
  65.             windowManager.addView(smallWindow, smallWindowParams);  
  66.         }  
  67.     }  
  68.   
  69.     /** 
  70.      * 将小悬浮窗从屏幕上移除。 
  71.      */  
  72.     public static void removeSmallWindow(Context context) {  
  73.         if (smallWindow != null) {  
  74.             WindowManager windowManager = getWindowManager(context);  
  75.             windowManager.removeView(smallWindow);  
  76.             smallWindow = null;  
  77.         }  
  78.     }  
  79.   
  80.     /** 
  81.      * 创建一个大悬浮窗。位置为屏幕正中间。 
  82.      */  
  83.     public static void createBigWindow(Context context) {  
  84.         WindowManager windowManager = getWindowManager(context);  
  85.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  86.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  87.         if (bigWindow == null) {  
  88.             bigWindow = new FloatWindowBigView(context);  
  89.             if (bigWindowParams == null) {  
  90.                 bigWindowParams = new LayoutParams();  
  91.                 bigWindowParams.x = screenWidth / 2  
  92.                         - FloatWindowBigView.viewWidth / 2;  
  93.                 bigWindowParams.y = screenHeight / 2  
  94.                         - FloatWindowBigView.viewHeight / 2;  
  95.                 bigWindowParams.type = LayoutParams.TYPE_PHONE;  
  96.                 bigWindowParams.format = PixelFormat.RGBA_8888;  
  97.                 bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
  98.                 bigWindowParams.width = FloatWindowBigView.viewWidth;  
  99.                 bigWindowParams.height = FloatWindowBigView.viewHeight;  
  100.             }  
  101.             windowManager.addView(bigWindow, bigWindowParams);  
  102.         }  
  103.     }  
  104.   
  105.     /** 
  106.      * 将大悬浮窗从屏幕上移除。 
  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.      * 创建一个火箭发射台,位置为屏幕底部。 
  118.      */  
  119.     public static void createLauncher(Context context) {  
  120.         WindowManager windowManager = getWindowManager(context);  
  121.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  122.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  123.         if (rocketLauncher == null) {  
  124.             rocketLauncher = new RocketLauncher(context);  
  125.             if (launcherParams == null) {  
  126.                 launcherParams = new LayoutParams();  
  127.                 launcherParams.x = screenWidth / 2 - RocketLauncher.width / 2;  
  128.                 launcherParams.y = screenHeight - RocketLauncher.height;  
  129.                 launcherParams.type = LayoutParams.TYPE_PHONE;  
  130.                 launcherParams.format = PixelFormat.RGBA_8888;  
  131.                 launcherParams.gravity = Gravity.LEFT | Gravity.TOP;  
  132.                 launcherParams.width = RocketLauncher.width;  
  133.                 launcherParams.height = RocketLauncher.height;  
  134.             }  
  135.             windowManager.addView(rocketLauncher, launcherParams);  
  136.         }  
  137.     }  
  138.   
  139.     /** 
  140.      * 将火箭发射台从屏幕上移除。 
  141.      */  
  142.     public static void removeLauncher(Context context) {  
  143.         if (rocketLauncher != null) {  
  144.             WindowManager windowManager = getWindowManager(context);  
  145.             windowManager.removeView(rocketLauncher);  
  146.             rocketLauncher = null;  
  147.         }  
  148.     }  
  149.   
  150.     /** 
  151.      * 更新火箭发射台的显示状态。 
  152.      */  
  153.     public static void updateLauncher() {  
  154.         if (rocketLauncher != null) {  
  155.             rocketLauncher.updateLauncherStatus(isReadyToLaunch());  
  156.         }  
  157.     }  
  158.   
  159.     /** 
  160.      * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。 
  161.      *  
  162.      * @param context 
  163.      *            可传入应用程序上下文。 
  164.      */  
  165.     public static void updateUsedPercent(Context context) {  
  166.         if (smallWindow != null) {  
  167.             TextView percentView = (TextView) smallWindow  
  168.                     .findViewById(R.id.percent);  
  169.             percentView.setText(getUsedPercentValue(context));  
  170.         }  
  171.     }  
  172.   
  173.     /** 
  174.      * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。 
  175.      *  
  176.      * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。 
  177.      */  
  178.     public static boolean isWindowShowing() {  
  179.         return smallWindow != null || bigWindow != null;  
  180.     }  
  181.   
  182.     /** 
  183.      * 判断小火箭是否准备好发射了。 
  184.      *  
  185.      * @return 当火箭被发到发射台上返回true,否则返回false。 
  186.      */  
  187.     public static boolean isReadyToLaunch() {  
  188.         if ((smallWindowParams.x > launcherParams.x && smallWindowParams.x  
  189.                 + smallWindowParams.width < launcherParams.x  
  190.                 + launcherParams.width)  
  191.                 && (smallWindowParams.y + smallWindowParams.height > launcherParams.y)) {  
  192.             return true;  
  193.         }  
  194.         return false;  
  195.     }  
  196.   
  197.     /** 
  198.      * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。 
  199.      *  
  200.      * @param context 
  201.      *            必须为应用程序的Context. 
  202.      * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。 
  203.      */  
  204.     private static WindowManager getWindowManager(Context context) {  
  205.         if (mWindowManager == null) {  
  206.             mWindowManager = (WindowManager) context  
  207.                     .getSystemService(Context.WINDOW_SERVICE);  
  208.         }  
  209.         return mWindowManager;  
  210.     }  
  211.   
  212.     /** 
  213.      * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。 
  214.      *  
  215.      * @param context 
  216.      *            可传入应用程序上下文。 
  217.      * @return ActivityManager的实例,用于获取手机可用内存。 
  218.      */  
  219.     private static ActivityManager getActivityManager(Context context) {  
  220.         if (mActivityManager == null) {  
  221.             mActivityManager = (ActivityManager) context  
  222.                     .getSystemService(Context.ACTIVITY_SERVICE);  
  223.         }  
  224.         return mActivityManager;  
  225.     }  
  226.   
  227.     /** 
  228.      * 计算已使用内存的百分比,并返回。 
  229.      *  
  230.      * @param context 
  231.      *            可传入应用程序上下文。 
  232.      * @return 已使用内存的百分比,以字符串形式返回。 
  233.      */  
  234.     public static String getUsedPercentValue(Context context) {  
  235.         String dir = "/proc/meminfo";  
  236.         try {  
  237.             FileReader fr = new FileReader(dir);  
  238.             BufferedReader br = new BufferedReader(fr, 2048);  
  239.             String memoryLine = br.readLine();  
  240.             String subMemoryLine = memoryLine.substring(memoryLine  
  241.                     .indexOf("MemTotal:"));  
  242.             br.close();  
  243.             long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll(  
  244.                     "\\D+", ""));  
  245.             long availableSize = getAvailableMemory(context) / 1024;  
  246.             int percent = (int) ((totalMemorySize - availableSize)  
  247.                     / (float) totalMemorySize * 100);  
  248.             return percent + "%";  
  249.         } catch (IOException e) {  
  250.             e.printStackTrace();  
  251.         }  
  252.         return "悬浮窗";  
  253.     }  
  254.   
  255.     /** 
  256.      * 获取当前可用内存,返回数据以字节为单位。 
  257.      *  
  258.      * @param context 
  259.      *            可传入应用程序上下文。 
  260.      * @return 当前可用内存。 
  261.      */  
  262.     private static long getAvailableMemory(Context context) {  
  263.         ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();  
  264.         getActivityManager(context).getMemoryInfo(mi);  
  265.         return mi.availMem;  
  266.     }  
  267.   
  268. }  
MyWindowManager是所有桌面悬浮窗的管理器,这里我们主要添加了createLauncher()、removeLauncher()和updateLauncher()这几个方法,分别用于创建、移除、以及更新火箭发射台悬浮窗。另外还添加了isReadyToLaunch()这个方法,它是用于判断小火箭是否已经拖动到火箭发射台上了。判断的方式当然也很简单,只需要对小火箭的边界和火箭发射台的边界进行检测,判断它们是否相交就行了。

接下来还需要修改FloatWindowSmallView中的代码,当手指拖动悬浮窗的时候要将它变成小火箭,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class FloatWindowSmallView extends LinearLayout {  
  2.   
  3.     /** 
  4.      * 记录小悬浮窗的宽度 
  5.      */  
  6.     public static int windowViewWidth;  
  7.   
  8.     /** 
  9.      * 记录小悬浮窗的高度 
  10.      */  
  11.     public static int windowViewHeight;  
  12.   
  13.     /** 
  14.      * 记录系统状态栏的高度 
  15.      */  
  16.     private static int statusBarHeight;  
  17.   
  18.     /** 
  19.      * 用于更新小悬浮窗的位置 
  20.      */  
  21.     private WindowManager windowManager;  
  22.   
  23.     /** 
  24.      * 小悬浮窗的布局 
  25.      */  
  26.     private LinearLayout smallWindowLayout;  
  27.   
  28.     /** 
  29.      * 小火箭控件 
  30.      */  
  31.     private ImageView rocketImg;  
  32.   
  33.     /** 
  34.      * 小悬浮窗的参数 
  35.      */  
  36.     private WindowManager.LayoutParams mParams;  
  37.   
  38.     /** 
  39.      * 记录当前手指位置在屏幕上的横坐标值 
  40.      */  
  41.     private float xInScreen;  
  42.   
  43.     /** 
  44.      * 记录当前手指位置在屏幕上的纵坐标值 
  45.      */  
  46.     private float yInScreen;  
  47.   
  48.     /** 
  49.      * 记录手指按下时在屏幕上的横坐标的值 
  50.      */  
  51.     private float xDownInScreen;  
  52.   
  53.     /** 
  54.      * 记录手指按下时在屏幕上的纵坐标的值 
  55.      */  
  56.     private float yDownInScreen;  
  57.   
  58.     /** 
  59.      * 记录手指按下时在小悬浮窗的View上的横坐标的值 
  60.      */  
  61.     private float xInView;  
  62.   
  63.     /** 
  64.      * 记录手指按下时在小悬浮窗的View上的纵坐标的值 
  65.      */  
  66.     private float yInView;  
  67.   
  68.     /** 
  69.      * 记录小火箭的宽度 
  70.      */  
  71.     private int rocketWidth;  
  72.   
  73.     /** 
  74.      * 记录小火箭的高度 
  75.      */  
  76.     private int rocketHeight;  
  77.   
  78.     /** 
  79.      * 记录当前手指是否按下 
  80.      */  
  81.     private boolean isPressed;  
  82.   
  83.     public FloatWindowSmallView(Context context) {  
  84.         super(context);  
  85.         windowManager = (WindowManager) context  
  86.                 .getSystemService(Context.WINDOW_SERVICE);  
  87.         LayoutInflater.from(context).inflate(R.layout.float_window_small, this);  
  88.         smallWindowLayout = (LinearLayout) findViewById(R.id.small_window_layout);  
  89.         windowViewWidth = smallWindowLayout.getLayoutParams().width;  
  90.         windowViewHeight = smallWindowLayout.getLayoutParams().height;  
  91.         rocketImg = (ImageView) findViewById(R.id.rocket_img);  
  92.         rocketWidth = rocketImg.getLayoutParams().width;  
  93.         rocketHeight = rocketImg.getLayoutParams().height;  
  94.         TextView percentView = (TextView) findViewById(R.id.percent);  
  95.         percentView.setText(MyWindowManager.getUsedPercentValue(context));  
  96.     }  
  97.   
  98.     @Override  
  99.     public boolean onTouchEvent(MotionEvent event) {  
  100.         switch (event.getAction()) {  
  101.         case MotionEvent.ACTION_DOWN:  
  102.             isPressed = true;  
  103.             // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度  
  104.             xInView = event.getX();  
  105.             yInView = event.getY();  
  106.             xDownInScreen = event.getRawX();  
  107.             yDownInScreen = event.getRawY() - getStatusBarHeight();  
  108.             xInScreen = event.getRawX();  
  109.             yInScreen = event.getRawY() - getStatusBarHeight();  
  110.             break;  
  111.         case MotionEvent.ACTION_MOVE:  
  112.             xInScreen = event.getRawX();  
  113.             yInScreen = event.getRawY() - getStatusBarHeight();  
  114.             // 手指移动的时候更新小悬浮窗的状态和位置  
  115.             updateViewStatus();  
  116.             updateViewPosition();  
  117.             break;  
  118.         case MotionEvent.ACTION_UP:  
  119.             isPressed = false;  
  120.             if (MyWindowManager.isReadyToLaunch()) {  
  121.                 launchRocket();  
  122.             } else {  
  123.                 updateViewStatus();  
  124.                 // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。  
  125.                 if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {  
  126.                     openBigWindow();  
  127.                 }  
  128.             }  
  129.             break;  
  130.         default:  
  131.             break;  
  132.         }  
  133.         return true;  
  134.     }  
  135.   
  136.     /** 
  137.      * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 
  138.      *  
  139.      * @param params 
  140.      *            小悬浮窗的参数 
  141.      */  
  142.     public void setParams(WindowManager.LayoutParams params) {  
  143.         mParams = params;  
  144.     }  
  145.   
  146.     /** 
  147.      * 用于发射小火箭。 
  148.      */  
  149.     private void launchRocket() {  
  150.         MyWindowManager.removeLauncher(getContext());  
  151.         new LaunchTask().execute();  
  152.     }  
  153.   
  154.     /** 
  155.      * 更新小悬浮窗在屏幕中的位置。 
  156.      */  
  157.     private void updateViewPosition() {  
  158.         mParams.x = (int) (xInScreen - xInView);  
  159.         mParams.y = (int) (yInScreen - yInView);  
  160.         windowManager.updateViewLayout(this, mParams);  
  161.         MyWindowManager.updateLauncher();  
  162.     }  
  163.   
  164.     /** 
  165.      * 更新View的显示状态,判断是显示悬浮窗还是小火箭。 
  166.      */  
  167.     private void updateViewStatus() {  
  168.         if (isPressed && rocketImg.getVisibility() != View.VISIBLE) {  
  169.             mParams.width = rocketWidth;  
  170.             mParams.height = rocketHeight;  
  171.             windowManager.updateViewLayout(this, mParams);  
  172.             smallWindowLayout.setVisibility(View.GONE);  
  173.             rocketImg.setVisibility(View.VISIBLE);  
  174.             MyWindowManager.createLauncher(getContext());  
  175.         } else if (!isPressed) {  
  176.             mParams.width = windowViewWidth;  
  177.             mParams.height = windowViewHeight;  
  178.             windowManager.updateViewLayout(this, mParams);  
  179.             smallWindowLayout.setVisibility(View.VISIBLE);  
  180.             rocketImg.setVisibility(View.GONE);  
  181.             MyWindowManager.removeLauncher(getContext());  
  182.         }  
  183.     }  
  184.   
  185.     /** 
  186.      * 打开大悬浮窗,同时关闭小悬浮窗。 
  187.      */  
  188.     private void openBigWindow() {  
  189.         MyWindowManager.createBigWindow(getContext());  
  190.         MyWindowManager.removeSmallWindow(getContext());  
  191.     }  
  192.   
  193.     /** 
  194.      * 用于获取状态栏的高度。 
  195.      *  
  196.      * @return 返回状态栏高度的像素值。 
  197.      */  
  198.     private int getStatusBarHeight() {  
  199.         if (statusBarHeight == 0) {  
  200.             try {  
  201.                 Class<?> c = Class.forName("com.android.internal.R$dimen");  
  202.                 Object o = c.newInstance();  
  203.                 Field field = c.getField("status_bar_height");  
  204.                 int x = (Integer) field.get(o);  
  205.                 statusBarHeight = getResources().getDimensionPixelSize(x);  
  206.             } catch (Exception e) {  
  207.                 e.printStackTrace();  
  208.             }  
  209.         }  
  210.         return statusBarHeight;  
  211.     }  
  212.   
  213.     /** 
  214.      * 开始执行发射小火箭的任务。 
  215.      *  
  216.      * @author guolin 
  217.      */  
  218.     class LaunchTask extends AsyncTask<Void, Void, Void> {  
  219.   
  220.         @Override  
  221.         protected Void doInBackground(Void... params) {  
  222.             // 在这里对小火箭的位置进行改变,从而产生火箭升空的效果  
  223.             while (mParams.y > 0) {  
  224.                 mParams.y = mParams.y - 10;  
  225.                 publishProgress();  
  226.                 try {  
  227.                     Thread.sleep(8);  
  228.                 } catch (InterruptedException e) {  
  229.                     e.printStackTrace();  
  230.                 }  
  231.             }  
  232.             return null;  
  233.         }  
  234.   
  235.         @Override  
  236.         protected void onProgressUpdate(Void... values) {  
  237.             windowManager.updateViewLayout(FloatWindowSmallView.this, mParams);  
  238.         }  
  239.   
  240.         @Override  
  241.         protected void onPostExecute(Void result) {  
  242.             // 火箭升空结束后,回归到悬浮窗状态  
  243.             updateViewStatus();  
  244.             mParams.x = (int) (xDownInScreen - xInView);  
  245.             mParams.y = (int) (yDownInScreen - yInView);  
  246.             windowManager.updateViewLayout(FloatWindowSmallView.this, mParams);  
  247.         }  
  248.   
  249.     }  
  250.   
  251. }  
这里在代码中添加了一个isPressed标识位,用于判断用户是否正在拖动悬浮窗。当拖动的时候就调用updateViewStatus()方法来更新悬浮窗的显示状态,这时悬浮窗就会变成一个小火箭。然后当手指离开屏幕的时候,也会调用updateViewStatus()方法,这时发现isPressed为false,就会将悬浮窗重新显示出来。

同时,当手指离开屏幕的时候,还会调用MyWindowManager的isReadyToLaunch()方法来判断小火箭是否被拖动到火箭发射台上了,如果为true,就会触发火箭升空的动画效果。火箭升空的动画实现是写在LaunchTask这个任务里的,可以看到,这里会在doInBackground()方法中执行耗时逻辑,将小火箭的纵坐标不断减小,以让它实现上升的效果。当纵坐标减小到0的时候,火箭升空的动画就结束了,然后在onPostExecute()方法中重新将悬浮窗显示出来。

另外,在AndroidManifest.xml文件中记得要声明两个权限,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  
  2. <uses-permission android:name="android.permission.GET_TASKS" />  
代码就只有这么多,接下来我们运行一下看看效果吧。在主界面点击Start Float Window按钮可以开启悬浮窗并回到桌面,然后拖动悬浮窗后就会变成小火箭的状态,将它拖动到屏幕底部火箭发射台上,然后放手,小火箭就会腾空而起了,如下图所示:

好了,今天的讲解就到这里,伴随着小火箭的起飞,我今年的最后一篇文章也结束了。

新的一年即将来临,祝愿大家在未来的一年里,无论是工作还是学习,都能像这个小火箭一样,腾飞起来,达到一个新的高度!2014年,我们继续共同努力!

源码下载,请点击这里

出处:http://blog.youkuaiyun.com/guolin_blog/article/details/16919859

今天是2013年的最后一天了,这里首先提前祝大家新年快乐!同时,本篇文章也是我今年的最后一篇文章了,因此我想要让它尽量有点特殊性,比起平时的文章要多一些特色。记得在今年年初的时候,我写的第一篇文章是模仿360手机卫士的桌面悬浮窗效果,那么为了能够首尾呼应,今年的最后一篇文章就同样还是来实现桌面悬浮窗的效果吧,当然效果将会更加高级。

相信用过QQ手机管家的朋友们都会知道它有一个小火箭加速的功能,将小火箭拖动到火箭发射台上发射就会出现一个火箭升空的动画,那么今天我们就来模仿着实现一下这个效果吧。

这次我们将代码的重点放在火箭升空的效果上,因此简单起见,就直接在模仿360手机卫士悬浮窗的那份代码的基础上继续开发了,如果你还没有看过那篇文章的话,建议先去阅读 Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果 。

比起普通的桌面悬浮窗,现在我们需要在拖动悬浮窗的时候将悬浮窗变成一个小火箭,并且在屏幕的底部添加一个火箭发射台。那么我们就从火箭发射台开始编写吧,首先创建launcher.xml作为火箭发射台的布局文件,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="wrap_content"  
  5.     android:layout_height="wrap_content"  
  6.     android:orientation="vertical"  
  7.     >  
  8.       
  9.     <ImageView   
  10.         android:id="@+id/launcher_img"  
  11.         android:layout_width="200dp"  
  12.         android:layout_height="88dp"  
  13.         android:src="@drawable/launcher_bg_hold"  
  14.         />  
  15.       
  16. </LinearLayout>  
可以看到,这里的ImageView是用于显示当前火箭发射台状态的。我事先准备好了两张图片,一张是当小火箭未拖动到火箭发射台时显示的,一张是当小火箭拖动到火箭发射台上时显示的。

接下来创建RocketLauncher类,作为火箭发射台的View,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class RocketLauncher extends LinearLayout {  
  2.   
  3.     /** 
  4.      * 记录火箭发射台的宽度 
  5.      */  
  6.     public static int width;  
  7.   
  8.     /** 
  9.      * 记录火箭发射台的高度 
  10.      */  
  11.     public static int height;  
  12.   
  13.     /** 
  14.      * 火箭发射台的背景图片 
  15.      */  
  16.     private ImageView launcherImg;  
  17.   
  18.     public RocketLauncher(Context context) {  
  19.         super(context);  
  20.         LayoutInflater.from(context).inflate(R.layout.launcher, this);  
  21.         launcherImg = (ImageView) findViewById(R.id.launcher_img);  
  22.         width = launcherImg.getLayoutParams().width;  
  23.         height = launcherImg.getLayoutParams().height;  
  24.     }  
  25.   
  26.     /** 
  27.      * 更新火箭发射台的显示状态。如果小火箭被拖到火箭发射台上,就显示发射。 
  28.      */  
  29.     public void updateLauncherStatus(boolean isReadyToLaunch) {  
  30.         if (isReadyToLaunch) {  
  31.             launcherImg.setImageResource(R.drawable.launcher_bg_fire);  
  32.         } else {  
  33.             launcherImg.setImageResource(R.drawable.launcher_bg_hold);  
  34.         }  
  35.     }  
  36.   
  37. }  
RocketLauncher中的代码还是非常简单的,在构建方法中调用了LayoutInflater的inflate()方法来将launcher.xml这个布局文件加载进来,并获取到了当前View的宽度和高度。在updateLauncherStatus()方法中会进行判断,如果传入的参数是true,就显示小火箭即将发射的图片,如果传入的是false,就显示将小火箭拖动到发射台的图片。

新增的文件只有这两个,剩下的就是要修改之前的代码了。首先修改MyWindowManager中的代码,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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.      * 火箭发射台的实例 
  15.      */  
  16.     private static RocketLauncher rocketLauncher;  
  17.   
  18.     /** 
  19.      * 小悬浮窗View的参数 
  20.      */  
  21.     private static LayoutParams smallWindowParams;  
  22.   
  23.     /** 
  24.      * 大悬浮窗View的参数 
  25.      */  
  26.     private static LayoutParams bigWindowParams;  
  27.   
  28.     /** 
  29.      * 火箭发射台的参数 
  30.      */  
  31.     private static LayoutParams launcherParams;  
  32.   
  33.     /** 
  34.      * 用于控制在屏幕上添加或移除悬浮窗 
  35.      */  
  36.     private static WindowManager mWindowManager;  
  37.   
  38.     /** 
  39.      * 用于获取手机可用内存 
  40.      */  
  41.     private static ActivityManager mActivityManager;  
  42.   
  43.     /** 
  44.      * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。 
  45.      */  
  46.     public static void createSmallWindow(Context context) {  
  47.         WindowManager windowManager = getWindowManager(context);  
  48.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  49.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  50.         if (smallWindow == null) {  
  51.             smallWindow = new FloatWindowSmallView(context);  
  52.             if (smallWindowParams == null) {  
  53.                 smallWindowParams = new LayoutParams();  
  54.                 smallWindowParams.type = LayoutParams.TYPE_SYSTEM_ALERT;  
  55.                 smallWindowParams.format = PixelFormat.RGBA_8888;  
  56.                 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL  
  57.                         | LayoutParams.FLAG_NOT_FOCUSABLE;  
  58.                 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
  59.                 smallWindowParams.width = FloatWindowSmallView.windowViewWidth;  
  60.                 smallWindowParams.height = FloatWindowSmallView.windowViewHeight;  
  61.                 smallWindowParams.x = screenWidth;  
  62.                 smallWindowParams.y = screenHeight / 2;  
  63.             }  
  64.             smallWindow.setParams(smallWindowParams);  
  65.             windowManager.addView(smallWindow, smallWindowParams);  
  66.         }  
  67.     }  
  68.   
  69.     /** 
  70.      * 将小悬浮窗从屏幕上移除。 
  71.      */  
  72.     public static void removeSmallWindow(Context context) {  
  73.         if (smallWindow != null) {  
  74.             WindowManager windowManager = getWindowManager(context);  
  75.             windowManager.removeView(smallWindow);  
  76.             smallWindow = null;  
  77.         }  
  78.     }  
  79.   
  80.     /** 
  81.      * 创建一个大悬浮窗。位置为屏幕正中间。 
  82.      */  
  83.     public static void createBigWindow(Context context) {  
  84.         WindowManager windowManager = getWindowManager(context);  
  85.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  86.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  87.         if (bigWindow == null) {  
  88.             bigWindow = new FloatWindowBigView(context);  
  89.             if (bigWindowParams == null) {  
  90.                 bigWindowParams = new LayoutParams();  
  91.                 bigWindowParams.x = screenWidth / 2  
  92.                         - FloatWindowBigView.viewWidth / 2;  
  93.                 bigWindowParams.y = screenHeight / 2  
  94.                         - FloatWindowBigView.viewHeight / 2;  
  95.                 bigWindowParams.type = LayoutParams.TYPE_PHONE;  
  96.                 bigWindowParams.format = PixelFormat.RGBA_8888;  
  97.                 bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
  98.                 bigWindowParams.width = FloatWindowBigView.viewWidth;  
  99.                 bigWindowParams.height = FloatWindowBigView.viewHeight;  
  100.             }  
  101.             windowManager.addView(bigWindow, bigWindowParams);  
  102.         }  
  103.     }  
  104.   
  105.     /** 
  106.      * 将大悬浮窗从屏幕上移除。 
  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.      * 创建一个火箭发射台,位置为屏幕底部。 
  118.      */  
  119.     public static void createLauncher(Context context) {  
  120.         WindowManager windowManager = getWindowManager(context);  
  121.         int screenWidth = windowManager.getDefaultDisplay().getWidth();  
  122.         int screenHeight = windowManager.getDefaultDisplay().getHeight();  
  123.         if (rocketLauncher == null) {  
  124.             rocketLauncher = new RocketLauncher(context);  
  125.             if (launcherParams == null) {  
  126.                 launcherParams = new LayoutParams();  
  127.                 launcherParams.x = screenWidth / 2 - RocketLauncher.width / 2;  
  128.                 launcherParams.y = screenHeight - RocketLauncher.height;  
  129.                 launcherParams.type = LayoutParams.TYPE_PHONE;  
  130.                 launcherParams.format = PixelFormat.RGBA_8888;  
  131.                 launcherParams.gravity = Gravity.LEFT | Gravity.TOP;  
  132.                 launcherParams.width = RocketLauncher.width;  
  133.                 launcherParams.height = RocketLauncher.height;  
  134.             }  
  135.             windowManager.addView(rocketLauncher, launcherParams);  
  136.         }  
  137.     }  
  138.   
  139.     /** 
  140.      * 将火箭发射台从屏幕上移除。 
  141.      */  
  142.     public static void removeLauncher(Context context) {  
  143.         if (rocketLauncher != null) {  
  144.             WindowManager windowManager = getWindowManager(context);  
  145.             windowManager.removeView(rocketLauncher);  
  146.             rocketLauncher = null;  
  147.         }  
  148.     }  
  149.   
  150.     /** 
  151.      * 更新火箭发射台的显示状态。 
  152.      */  
  153.     public static void updateLauncher() {  
  154.         if (rocketLauncher != null) {  
  155.             rocketLauncher.updateLauncherStatus(isReadyToLaunch());  
  156.         }  
  157.     }  
  158.   
  159.     /** 
  160.      * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。 
  161.      *  
  162.      * @param context 
  163.      *            可传入应用程序上下文。 
  164.      */  
  165.     public static void updateUsedPercent(Context context) {  
  166.         if (smallWindow != null) {  
  167.             TextView percentView = (TextView) smallWindow  
  168.                     .findViewById(R.id.percent);  
  169.             percentView.setText(getUsedPercentValue(context));  
  170.         }  
  171.     }  
  172.   
  173.     /** 
  174.      * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。 
  175.      *  
  176.      * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。 
  177.      */  
  178.     public static boolean isWindowShowing() {  
  179.         return smallWindow != null || bigWindow != null;  
  180.     }  
  181.   
  182.     /** 
  183.      * 判断小火箭是否准备好发射了。 
  184.      *  
  185.      * @return 当火箭被发到发射台上返回true,否则返回false。 
  186.      */  
  187.     public static boolean isReadyToLaunch() {  
  188.         if ((smallWindowParams.x > launcherParams.x && smallWindowParams.x  
  189.                 + smallWindowParams.width < launcherParams.x  
  190.                 + launcherParams.width)  
  191.                 && (smallWindowParams.y + smallWindowParams.height > launcherParams.y)) {  
  192.             return true;  
  193.         }  
  194.         return false;  
  195.     }  
  196.   
  197.     /** 
  198.      * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。 
  199.      *  
  200.      * @param context 
  201.      *            必须为应用程序的Context. 
  202.      * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。 
  203.      */  
  204.     private static WindowManager getWindowManager(Context context) {  
  205.         if (mWindowManager == null) {  
  206.             mWindowManager = (WindowManager) context  
  207.                     .getSystemService(Context.WINDOW_SERVICE);  
  208.         }  
  209.         return mWindowManager;  
  210.     }  
  211.   
  212.     /** 
  213.      * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。 
  214.      *  
  215.      * @param context 
  216.      *            可传入应用程序上下文。 
  217.      * @return ActivityManager的实例,用于获取手机可用内存。 
  218.      */  
  219.     private static ActivityManager getActivityManager(Context context) {  
  220.         if (mActivityManager == null) {  
  221.             mActivityManager = (ActivityManager) context  
  222.                     .getSystemService(Context.ACTIVITY_SERVICE);  
  223.         }  
  224.         return mActivityManager;  
  225.     }  
  226.   
  227.     /** 
  228.      * 计算已使用内存的百分比,并返回。 
  229.      *  
  230.      * @param context 
  231.      *            可传入应用程序上下文。 
  232.      * @return 已使用内存的百分比,以字符串形式返回。 
  233.      */  
  234.     public static String getUsedPercentValue(Context context) {  
  235.         String dir = "/proc/meminfo";  
  236.         try {  
  237.             FileReader fr = new FileReader(dir);  
  238.             BufferedReader br = new BufferedReader(fr, 2048);  
  239.             String memoryLine = br.readLine();  
  240.             String subMemoryLine = memoryLine.substring(memoryLine  
  241.                     .indexOf("MemTotal:"));  
  242.             br.close();  
  243.             long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll(  
  244.                     "\\D+", ""));  
  245.             long availableSize = getAvailableMemory(context) / 1024;  
  246.             int percent = (int) ((totalMemorySize - availableSize)  
  247.                     / (float) totalMemorySize * 100);  
  248.             return percent + "%";  
  249.         } catch (IOException e) {  
  250.             e.printStackTrace();  
  251.         }  
  252.         return "悬浮窗";  
  253.     }  
  254.   
  255.     /** 
  256.      * 获取当前可用内存,返回数据以字节为单位。 
  257.      *  
  258.      * @param context 
  259.      *            可传入应用程序上下文。 
  260.      * @return 当前可用内存。 
  261.      */  
  262.     private static long getAvailableMemory(Context context) {  
  263.         ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();  
  264.         getActivityManager(context).getMemoryInfo(mi);  
  265.         return mi.availMem;  
  266.     }  
  267.   
  268. }  
MyWindowManager是所有桌面悬浮窗的管理器,这里我们主要添加了createLauncher()、removeLauncher()和updateLauncher()这几个方法,分别用于创建、移除、以及更新火箭发射台悬浮窗。另外还添加了isReadyToLaunch()这个方法,它是用于判断小火箭是否已经拖动到火箭发射台上了。判断的方式当然也很简单,只需要对小火箭的边界和火箭发射台的边界进行检测,判断它们是否相交就行了。

接下来还需要修改FloatWindowSmallView中的代码,当手指拖动悬浮窗的时候要将它变成小火箭,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class FloatWindowSmallView extends LinearLayout {  
  2.   
  3.     /** 
  4.      * 记录小悬浮窗的宽度 
  5.      */  
  6.     public static int windowViewWidth;  
  7.   
  8.     /** 
  9.      * 记录小悬浮窗的高度 
  10.      */  
  11.     public static int windowViewHeight;  
  12.   
  13.     /** 
  14.      * 记录系统状态栏的高度 
  15.      */  
  16.     private static int statusBarHeight;  
  17.   
  18.     /** 
  19.      * 用于更新小悬浮窗的位置 
  20.      */  
  21.     private WindowManager windowManager;  
  22.   
  23.     /** 
  24.      * 小悬浮窗的布局 
  25.      */  
  26.     private LinearLayout smallWindowLayout;  
  27.   
  28.     /** 
  29.      * 小火箭控件 
  30.      */  
  31.     private ImageView rocketImg;  
  32.   
  33.     /** 
  34.      * 小悬浮窗的参数 
  35.      */  
  36.     private WindowManager.LayoutParams mParams;  
  37.   
  38.     /** 
  39.      * 记录当前手指位置在屏幕上的横坐标值 
  40.      */  
  41.     private float xInScreen;  
  42.   
  43.     /** 
  44.      * 记录当前手指位置在屏幕上的纵坐标值 
  45.      */  
  46.     private float yInScreen;  
  47.   
  48.     /** 
  49.      * 记录手指按下时在屏幕上的横坐标的值 
  50.      */  
  51.     private float xDownInScreen;  
  52.   
  53.     /** 
  54.      * 记录手指按下时在屏幕上的纵坐标的值 
  55.      */  
  56.     private float yDownInScreen;  
  57.   
  58.     /** 
  59.      * 记录手指按下时在小悬浮窗的View上的横坐标的值 
  60.      */  
  61.     private float xInView;  
  62.   
  63.     /** 
  64.      * 记录手指按下时在小悬浮窗的View上的纵坐标的值 
  65.      */  
  66.     private float yInView;  
  67.   
  68.     /** 
  69.      * 记录小火箭的宽度 
  70.      */  
  71.     private int rocketWidth;  
  72.   
  73.     /** 
  74.      * 记录小火箭的高度 
  75.      */  
  76.     private int rocketHeight;  
  77.   
  78.     /** 
  79.      * 记录当前手指是否按下 
  80.      */  
  81.     private boolean isPressed;  
  82.   
  83.     public FloatWindowSmallView(Context context) {  
  84.         super(context);  
  85.         windowManager = (WindowManager) context  
  86.                 .getSystemService(Context.WINDOW_SERVICE);  
  87.         LayoutInflater.from(context).inflate(R.layout.float_window_small, this);  
  88.         smallWindowLayout = (LinearLayout) findViewById(R.id.small_window_layout);  
  89.         windowViewWidth = smallWindowLayout.getLayoutParams().width;  
  90.         windowViewHeight = smallWindowLayout.getLayoutParams().height;  
  91.         rocketImg = (ImageView) findViewById(R.id.rocket_img);  
  92.         rocketWidth = rocketImg.getLayoutParams().width;  
  93.         rocketHeight = rocketImg.getLayoutParams().height;  
  94.         TextView percentView = (TextView) findViewById(R.id.percent);  
  95.         percentView.setText(MyWindowManager.getUsedPercentValue(context));  
  96.     }  
  97.   
  98.     @Override  
  99.     public boolean onTouchEvent(MotionEvent event) {  
  100.         switch (event.getAction()) {  
  101.         case MotionEvent.ACTION_DOWN:  
  102.             isPressed = true;  
  103.             // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度  
  104.             xInView = event.getX();  
  105.             yInView = event.getY();  
  106.             xDownInScreen = event.getRawX();  
  107.             yDownInScreen = event.getRawY() - getStatusBarHeight();  
  108.             xInScreen = event.getRawX();  
  109.             yInScreen = event.getRawY() - getStatusBarHeight();  
  110.             break;  
  111.         case MotionEvent.ACTION_MOVE:  
  112.             xInScreen = event.getRawX();  
  113.             yInScreen = event.getRawY() - getStatusBarHeight();  
  114.             // 手指移动的时候更新小悬浮窗的状态和位置  
  115.             updateViewStatus();  
  116.             updateViewPosition();  
  117.             break;  
  118.         case MotionEvent.ACTION_UP:  
  119.             isPressed = false;  
  120.             if (MyWindowManager.isReadyToLaunch()) {  
  121.                 launchRocket();  
  122.             } else {  
  123.                 updateViewStatus();  
  124.                 // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。  
  125.                 if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {  
  126.                     openBigWindow();  
  127.                 }  
  128.             }  
  129.             break;  
  130.         default:  
  131.             break;  
  132.         }  
  133.         return true;  
  134.     }  
  135.   
  136.     /** 
  137.      * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 
  138.      *  
  139.      * @param params 
  140.      *            小悬浮窗的参数 
  141.      */  
  142.     public void setParams(WindowManager.LayoutParams params) {  
  143.         mParams = params;  
  144.     }  
  145.   
  146.     /** 
  147.      * 用于发射小火箭。 
  148.      */  
  149.     private void launchRocket() {  
  150.         MyWindowManager.removeLauncher(getContext());  
  151.         new LaunchTask().execute();  
  152.     }  
  153.   
  154.     /** 
  155.      * 更新小悬浮窗在屏幕中的位置。 
  156.      */  
  157.     private void updateViewPosition() {  
  158.         mParams.x = (int) (xInScreen - xInView);  
  159.         mParams.y = (int) (yInScreen - yInView);  
  160.         windowManager.updateViewLayout(this, mParams);  
  161.         MyWindowManager.updateLauncher();  
  162.     }  
  163.   
  164.     /** 
  165.      * 更新View的显示状态,判断是显示悬浮窗还是小火箭。 
  166.      */  
  167.     private void updateViewStatus() {  
  168.         if (isPressed && rocketImg.getVisibility() != View.VISIBLE) {  
  169.             mParams.width = rocketWidth;  
  170.             mParams.height = rocketHeight;  
  171.             windowManager.updateViewLayout(this, mParams);  
  172.             smallWindowLayout.setVisibility(View.GONE);  
  173.             rocketImg.setVisibility(View.VISIBLE);  
  174.             MyWindowManager.createLauncher(getContext());  
  175.         } else if (!isPressed) {  
  176.             mParams.width = windowViewWidth;  
  177.             mParams.height = windowViewHeight;  
  178.             windowManager.updateViewLayout(this, mParams);  
  179.             smallWindowLayout.setVisibility(View.VISIBLE);  
  180.             rocketImg.setVisibility(View.GONE);  
  181.             MyWindowManager.removeLauncher(getContext());  
  182.         }  
  183.     }  
  184.   
  185.     /** 
  186.      * 打开大悬浮窗,同时关闭小悬浮窗。 
  187.      */  
  188.     private void openBigWindow() {  
  189.         MyWindowManager.createBigWindow(getContext());  
  190.         MyWindowManager.removeSmallWindow(getContext());  
  191.     }  
  192.   
  193.     /** 
  194.      * 用于获取状态栏的高度。 
  195.      *  
  196.      * @return 返回状态栏高度的像素值。 
  197.      */  
  198.     private int getStatusBarHeight() {  
  199.         if (statusBarHeight == 0) {  
  200.             try {  
  201.                 Class<?> c = Class.forName("com.android.internal.R$dimen");  
  202.                 Object o = c.newInstance();  
  203.                 Field field = c.getField("status_bar_height");  
  204.                 int x = (Integer) field.get(o);  
  205.                 statusBarHeight = getResources().getDimensionPixelSize(x);  
  206.             } catch (Exception e) {  
  207.                 e.printStackTrace();  
  208.             }  
  209.         }  
  210.         return statusBarHeight;  
  211.     }  
  212.   
  213.     /** 
  214.      * 开始执行发射小火箭的任务。 
  215.      *  
  216.      * @author guolin 
  217.      */  
  218.     class LaunchTask extends AsyncTask<Void, Void, Void> {  
  219.   
  220.         @Override  
  221.         protected Void doInBackground(Void... params) {  
  222.             // 在这里对小火箭的位置进行改变,从而产生火箭升空的效果  
  223.             while (mParams.y > 0) {  
  224.                 mParams.y = mParams.y - 10;  
  225.                 publishProgress();  
  226.                 try {  
  227.                     Thread.sleep(8);  
  228.                 } catch (InterruptedException e) {  
  229.                     e.printStackTrace();  
  230.                 }  
  231.             }  
  232.             return null;  
  233.         }  
  234.   
  235.         @Override  
  236.         protected void onProgressUpdate(Void... values) {  
  237.             windowManager.updateViewLayout(FloatWindowSmallView.this, mParams);  
  238.         }  
  239.   
  240.         @Override  
  241.         protected void onPostExecute(Void result) {  
  242.             // 火箭升空结束后,回归到悬浮窗状态  
  243.             updateViewStatus();  
  244.             mParams.x = (int) (xDownInScreen - xInView);  
  245.             mParams.y = (int) (yDownInScreen - yInView);  
  246.             windowManager.updateViewLayout(FloatWindowSmallView.this, mParams);  
  247.         }  
  248.   
  249.     }  
  250.   
  251. }  
这里在代码中添加了一个isPressed标识位,用于判断用户是否正在拖动悬浮窗。当拖动的时候就调用updateViewStatus()方法来更新悬浮窗的显示状态,这时悬浮窗就会变成一个小火箭。然后当手指离开屏幕的时候,也会调用updateViewStatus()方法,这时发现isPressed为false,就会将悬浮窗重新显示出来。

同时,当手指离开屏幕的时候,还会调用MyWindowManager的isReadyToLaunch()方法来判断小火箭是否被拖动到火箭发射台上了,如果为true,就会触发火箭升空的动画效果。火箭升空的动画实现是写在LaunchTask这个任务里的,可以看到,这里会在doInBackground()方法中执行耗时逻辑,将小火箭的纵坐标不断减小,以让它实现上升的效果。当纵坐标减小到0的时候,火箭升空的动画就结束了,然后在onPostExecute()方法中重新将悬浮窗显示出来。

另外,在AndroidManifest.xml文件中记得要声明两个权限,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  
  2. <uses-permission android:name="android.permission.GET_TASKS" />  
代码就只有这么多,接下来我们运行一下看看效果吧。在主界面点击Start Float Window按钮可以开启悬浮窗并回到桌面,然后拖动悬浮窗后就会变成小火箭的状态,将它拖动到屏幕底部火箭发射台上,然后放手,小火箭就会腾空而起了,如下图所示:

好了,今天的讲解就到这里,伴随着小火箭的起飞,我今年的最后一篇文章也结束了。

新的一年即将来临,祝愿大家在未来的一年里,无论是工作还是学习,都能像这个小火箭一样,腾飞起来,达到一个新的高度!2014年,我们继续共同努力!

Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果 源码程序 此为实例源码,详细讲解请参考博文http://blog.youkuaiyun.com/sinyu890807/article/details/8689140 立即下载

相关推荐

小火箭特效-安卓android源码完整版

android游戏源码,安卓项目源码,安卓源码,android源码,安卓开发

修改android-自定义控件-悬浮控件-仿360手机助手应用详情页

由于近期项目的需求的增加,要展示一个详情界面,大致是顶部是一个ScrollView或者是一个LinearLayout布局, 中间有个悬浮层,一般都是ViewPager最下面基本是一些自定义的Fragment。这种控件在很多商品详情界面都存在 到网上查找,最后发现一个类型的控件,根据自己的修改,完成了功能,特此写篇博客记录一下 public class StickyNavLayout exte

lizubing1992的专栏 1258

Android仿QQ悬浮窗

Android仿的QQ管家悬浮窗,可以发射小火箭

android桌面悬浮窗效果进阶 仿360手机卫士、淘宝手机助手-IT计算机-毕业设计.zip

Android应用源码开发Demo,主要用于毕业设计学习。

360悬浮加速小火箭,内存清理,加速缓存

最近时间都比较的紧,处理不同项目的BUG,今天偷得半日闲,把剩下项目的一部分扔给了组长。有个问题没解决,做了两天也没完成,实在是没有办法了。也很感谢,旅行途中遇到很多有趣的人,希望他们越来越顺利。 废话少说,接入今天的主题。 需求文档 1、在主界面 显示 一个悬浮图标或者入口,点击后展示如下界面,直接开始清理,清理完成后,弹个toast提示(已清理多少空间),就退出界面了 添加依赖库: //获取运行进程 implementation 'com.jaredrummler:android-processes:1.0.9' 同步之后,使用如下获取正在运行List List mProce

【android】仿360手机卫士的简易设计思路及源码

笔者最近一直忙于满广州的跑,实习好难找好难找,博客也是有点久没去更新。仿360手机卫士的实现的目的更多的是出于对常用知识点的一个巩固吧,比较适合像我这种接触没多久的学习者在学习之余拿来练手保持写代码的感觉的的一个不错的小项目。       涉及的技术:      都是些非常常用的android常用开发,但涉及面比较广吧。(小编这边也谈不出啥新意,就简简单单举几个小例子,见谅见谅~!

zjngogo的博客 1万+

从稀疏表示到低秩表示(一)

确定研究方向后一直在狂补理论,最近看了一些文章,有了些想法,顺便也总结了representation系列的文章,由于我刚接触,可能会有些不足,愿大家共同指正。 从稀疏表示到低秩表示系列文章包括如下内容: 一、 sparse representation 二、 一、 sparse representation 1  sparsity  一个线性表示解决的问题如下图所示:

彼岸花 3万+

android手机安全卫士源码高仿360安全卫士

│  .classpath │  .project │  address.db │  AndroidManifest.xml │  lint.xml │  proguard.cfg │  project.properties │  javaapk.com文件列表生成工具.bat │  目录列表.txt │   ├─.settings │      org.eclipse.jd

安卓源码下载 2779

赛灵思KV260视觉AI套件实战:从模型到边缘部署全流程解析

边缘计算与AI推理加速是当前嵌入式系统和物联网领域的关键技术。其核心原理在于将计算任务从云端下沉至网络边缘的设备端,通过专用硬件加速器(如FPGA、ASIC或NPU)执行神经网络推理,从而显著降低延迟、减少带宽消耗并提升数据隐私性。这一技术为实时性要求高的应用场景(如工业视觉检测、智能安防、自动驾驶等)提供了关键支撑。以赛灵思(Xilinx)的Zynq UltraScale+ MPSoC平台为例,其集成了ARM处理器与可编程逻辑,通过预置的深度学习处理器单元(DPU)为卷积神经网络提供高效INT8加速。开发

weixin_28719225的博客 326

Cisco思科 电子科技大学计算机网络 SRWE 期末技能考试 (PTSA)

电子科技大学2024春_SRWE_计算机网络系统 SRWE 期末技能考试 (PTSA)

2301_76745065的博客 5097

Java安卓如何添加悬浮窗_Android桌面悬浮窗效果实现

360手机卫士我相信大家都知道,好多人手机上都会装这一款软件,那么我们对它的一个桌面悬浮窗效果想必都不会陌生。请看下图:首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速。好,我们现在就来模拟实现一下类似的效果。先谈一下基本的实现原理,这种桌面悬浮窗的效果很类似与Widget,但是它比Widget要灵活的多。主要是通过WindowMana...

weixin_32562455的博客 1465

说一下Intel Fortran 编译器经典版ifort和新一代ifx

摘要:ifort是Intel开发的传统Fortran编译器,以稳定性和遗留代码兼容性著称,现已进入维护模式。ifx则是基于LLVM架构的新一代Fortran编译器,具有更好的性能优化、更快的编译速度,并支持最新Fortran标准和GPU加速。英特尔计划逐步用ifx取代ifort,前者将成为未来主力发展的产品线。两者的核心区别在于技术基础(传统vs.现代化LLVM)和功能支持(维护状态vs.持续更新)。

luohaitao的专栏 1347

Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现

相信用过QQ手机管家的朋友们都会知道它有一个小火箭加速的功能,将小火箭拖动到火箭发射台上发射就会出现一个火箭升空的动画,那么今天我们就来模仿着实现一下这个效果吧。这次我们将代码的重点放在火箭升空的效果上,因此简单起见,就直接在模仿360手机卫士悬浮窗的那份代码的基础上继续开发了,如果你还没有看过那篇文章的话,建议先去阅读Android桌面悬浮窗效果实现,仿360手机卫士悬浮...

weixin_34007906的博客 326

Prompt Airlines CTF通关实战:从提示词注入到多模态攻击的AI安全攻防演练

提示词注入(Prompt Injection)是当前大语言模型(LLM)应用面临的核心安全威胁之一,其原理在于攻击者通过精心构造的用户输入,覆盖或绕过系统预设的指令,从而操控模型行为。这种攻击手法的技术价值在于揭示了AI系统在理解与执行指令时存在的逻辑边界模糊性,可能导致数据泄露、越权访问等严重风险。在应用场景上,从基础的客服对话到结合视觉识别的多模态系统,都可能成为攻击面。本文以一场名为“Prompt Airlines CTF”的实战挑战为例,深入剖析了包括指令遗忘、上下文注入、工具调用滥用及多模态提示注

aodan5477的博客 523

eMMC数据擦除的方式

接下来主要是讲解erase、trim、discard的差异,涉及的command有CMD35、CMD36、CMD38。一般用来擦除整卡或者某些分区,如果只想擦除一个。以下是device status的部分解释。eMMC的数据擦除方式有6种,分别是。将会跳过写保护,只执行其他的范围。Discard的命令序列是。Erase的命令序列是。的范围有包含写保护,Trim的命令序列是。

DPJimmy的博客 1774

Android仿腾讯手机管家实现桌面悬浮窗小火箭发射的动画效果

模仿腾讯手机管家实现桌面悬浮窗小火箭发射的动画效果 1、小火箭游离在activity之外,不依附于任何activity,不管activity是否开启,不影响小火箭的代码逻辑,所以小火箭的代码逻辑是要写在服务中; 2、小火箭挂载在手机窗体之上; 3、小火箭喷射火焰,两种图片轮流展示的动画集; 4、小火箭可以拖拽到任意位置; 5、小火箭拖拽到指定区域才可以触发发射效果; 6、小火箭喷射尾气动画效果;

neuHenry 4428

【AI智能体】Coze 打通MySql实现数据报表展示应用搭建详解

Coze 打通MySql实现数据报表展示应用搭建详解

congge 3857

android 360 悬浮窗口,Android实现类似360,QQ管家那样的悬浮窗

一、前言:我手机从来不装这些东西,不过,有次看到同事的android手机上,有个QQ管家在桌面上浮着,同事拖动管家时,管家就变成一只鸟,桌面下方还有个弹弓,桌面顶部有只乌鸦,把管家也就是鸟拖动到弹弓那,然后,松手,鸟就飞出去。这个过程是动画过程,做的事,实际上是清楚内存。二:原理:其实,没什么原理,用到的就是WindowManager以及WindowManager.LayoutParams,对这个...

weixin_42681774的博客 331

提示工程架构师干货:AI上下文工程在社交媒体舆情溯源中的应用

在舆情溯源中,“上下文”不是单一的“文本内容”,而是多维度、多源的信息集合溯源时间:从事件爆发到找到源头,只用了24小时(传统方法需要3-5天);溯源准确性:100%(通过人工验证确认);后续行动:品牌X发布了澄清声明,揭露了品牌Y的恶意行为,舆情在48小时内平息。社交媒体舆情溯源是一个“复杂的系统工程”,需要结合数据、模型、上下文理解。AI上下文工程的出现,让我们从“被动应对”转向“主动溯源”,从“模糊判断”转向“精准分析”。

Java大师兄的博客 813

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

完整版仿360等手机卫士火箭拖动版

先看下效果图:                                           原理: 绳子设置: bitmapShader=new BitmapShader(shengzi, Shader.TileMode.REPEAT,Shader.TileMode.REPEAT); paint.setShader(bitmapShader); 画绳子: path.mo

hunanqi的博客 1025

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

安卓项目实战之Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

https://blog.youkuaiyun.com/guolin_blog/article/details/8689140

智玲君 625

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: Android Volley完全解析
下一篇: android:TableLayout表格布局详解
cicilover
博客等级 码龄18年 47粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值