GVKun编程网logo

Android实现桌面悬浮小火箭效果(android实现桌面悬浮小火箭效果怎么设置)

29

关于Android实现桌面悬浮小火箭效果和android实现桌面悬浮小火箭效果怎么设置的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Android实现桌面未读角标、Android桌面悬浮

关于Android实现桌面悬浮小火箭效果android实现桌面悬浮小火箭效果怎么设置的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Android 实现桌面未读角标、Android 桌面悬浮窗效果实现,仿 360 手机卫士悬浮窗效果、Android仿360悬浮小球自定义view实现示例、Android仿360桌面手机卫士悬浮窗效果等相关知识的信息别忘了在本站进行查找喔。

本文目录一览:

Android实现桌面悬浮小火箭效果(android实现桌面悬浮小火箭效果怎么设置)

Android实现桌面悬浮小火箭效果(android实现桌面悬浮小火箭效果怎么设置)

本文实例为大家分享了Android实现悬浮小火箭效果的具体代码,供大家参考,具体内容如下

思路

使用serivce在后台启动小火箭
小火箭使用windowmanager实现。
用ontoch监听实现小火箭的拖拽。

代码实现

public class RocketService extends Service {

   private WindowManager mWM;
   private View view;
   private int startX ;
   private int startY ;
   private LayoutParams params;

   @Override
   public IBinder onBind(Intent intent) {
      return null ;
   }

   @Override
   public void onCreate() {
      super .onCreate();
     System. out .println("服务创建。。。。" );
      mWM = (WindowManager) getSystemService(WINDOW_SERVICE );
      winWidth = mWM.getDefaultdisplay(). getWidth();
      winHeight = mWM .getDefaultdisplay().getHeight ();

      params = new WindowManager.LayoutParams();
      params. height = WindowManager.LayoutParams.WRAP_CONTENT ;
      params. width = WindowManager.LayoutParams.WRAP_CONTENT ;
      params. flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
          | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;
      params. format = PixelFormat. TRANSLUCENT ;
      params. type = WindowManager.LayoutParams.TYPE_PHONE ;
      params. gravity = Gravity. LEFT + Gravity. TOP;

      view = LayoutInflater.from( this).inflate(R.layout. rocket,null );
      //拿到 imageview,设置帧动画
     ImageView ivRocket = (ImageView) view .findViewById(R.id. rocket);
     ivRocket.setimageResource(R.drawable. rocket );
     AnimationDrawable drawable = (AnimationDrawable) ivRocket.getDrawable();
     drawable.start();
      mWM.addView( view,params);

      // 设置view的触摸事件,让它可以被拖拽
      view.setonTouchListener( new OnTouchListener() {

        @Override
        public boolean onTouch(View v,MotionEvent event) {
          switch (event.getAction()) {
          case MotionEvent. ACTION_DOWN:
             startX = ( int ) event.getRawX();
             startY = ( int ) event.getRawY();
             break ;
          case MotionEvent. ACTION_MOVE:
             int dx = (int ) (event.getRawX() - startX );
             int dy = (int ) (event.getRawY() - startY );
             // 更新浮窗位置
             params. x += dx;
             params. y += dy;

             // 限制窗口坐标不超过屏幕
             if (params .x < 0) {
               params. x = 0;
            }

             if (params .x > winWidth - view .getWidth()) {
               params. x = winWidth - view .getWidth();
            }

             if (params .y < 0) {
               params. y = 0;
            }

             if (params .y > winHeight - view .getHeight()) {
               params. y = winHeight - view .getHeight();
            }

             mWM.updateViewLayout( view,params );
             startX = ( int ) event.getRawX();
             startY = ( int ) event.getRawY();
             break ;
          case MotionEvent. ACTION_UP:
             // 手指抬起起,需要发射火箭,限定发射火箭的范围
             if (params .x > 0 && params. x < winWidth
                 && params. y > winHeight - 500) {
               sendRocket();
            }
             break ;
          }

          return true ;// 不再把事件传递给onClick处理
       }

     });
   }

   private Handler mHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
          int y = msg.arg1 ;
          params. y = y;
          mWM.updateViewLayout( view,params);
     }
   };
   private int winWidth ;
   private int winHeight ;

   // 发射火箭
   private void sendRocket() {
      // 用子线程更新y轴
      new Thread(new Runnable() {

        @Override
        public void run() {
          int pos = 1000;
          for (int i=0; i <= 10; i++) {
               int y = pos-100*i;
               //休眠100ms发消息
               try {
                 Thread. sleep(100);
               } catch (InterruptedException e) {
                  // Todo Auto-generated catch block
                 e.printstacktrace();
               }

               Message msg = Message.obtain();
               msg. arg1 = y;
               mHandler.sendMessage(msg);
          }
       }
     }).start();
   }

   @Override
   public void onDestroy() {
      // Todo Auto-generated method stub
      super .onDestroy();
      if (mWM != null && view != null) {
        mWM.removeView( view);
        view = null ;
     }

   }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

Android 实现桌面未读角标

Android 实现桌面未读角标

在小米 三星  索尼 手机 :图标上显示数字(未读消息数):这部分代码,是从QQ5.0.apk中找的。

小米已经测试通过了,

三星和索尼的,由于没有相应的手机,没有测试,有的,可能修改一下代码(判断是什么手机的代码),

测试一下,可以在回复一下测试结果,谢谢

1.原生系统(原生的Launcher ),只能修改快捷方式,增加和删除都会有toast提示

2.小米 三星  索尼 手机: 自定义的launcher:  发送显示未读消息数的action已经不同了。具体可以去看代码。。。

判断手机的代码:

/***
     * 在应用图标的快捷方式上加数字
     * @param clazz 启动的activity
     * @param isShowNum 是否显示数字
     * @param num 显示的数字:整型
     * @param isstroke 是否加上边框
     * 
     */
    public static void addNumShortCut(Context context,Class<?> clazz,boolean isShowNum,String num,boolean isstroke)
     {
         Log.e(TAG,"manufacturer="+Build.MANUFACTURER);
      if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")){
          //小米
       xiaoMiShortCut(context,clazz,num);

      }else if(Build.MANUFACTURER.equalsIgnoreCase("samsung")){
          //三星
          samsungShortCut(context,num);

      }else {//其他原生系统手机
          installrawShortCut(context,MainActivity.class,isShowNum,num,isstroke);
      }

     }

下面写好的工具类,上面的方法也在其中。

/***
* 应用的快捷方式工具类
* 
* @author yang
* 
*/
public class AppShortCutUtil {

    private static final String TAG = "AppShortCutUtil";

    //默认圆角半径
    private static final int DEFAULT_CORNER_RADIUS_DIP = 8;
    //默认边框宽度
    private static final int DEFAULT_stroke_WIDTH_DIP = 2;
    //边框的颜色
    private static final int DEFAULT_stroke_COLOR = Color.WHITE;
    //中间数字的颜色
    private static final int DEFAULT_NUM_COLOR = Color.parseColor("#CCFF0000");

    /***
     * 
     * 生成有数字的图片(没有边框)
     * @param context
     * @param icon 图片
     * @param isShowNum 是否要绘制数字
     * @param num 数字字符串:整型数字 超过99,显示为"99+"
     * @return
     */
    public static Bitmap generatorNumIcon(Context context,Bitmap icon,String num) {

        displayMetrics dm = context.getResources().getdisplayMetrics();
        //基准屏幕密度
        float baseDensity = 1.5f;//240dpi
        float factor = dm.density/baseDensity;

        Log.e(TAG,"density:"+dm.density);
        Log.e(TAG,"dpi:"+dm.densityDpi);
        Log.e(TAG,"factor:"+factor);

        // 初始化画布
        int iconSize = (int) context.getResources().getDimension(android.R.dimen.app_icon_size);
        Bitmap numIcon = Bitmap.createBitmap(iconSize,iconSize,Config.ARGB_8888);
        Canvas canvas = new Canvas(numIcon);

        // 拷贝图片
        Paint iconPaint = new Paint();
        iconPaint.setDither(true);// 防抖动
        iconPaint.setFilterBitmap(true);// 用来对Bitmap进行滤波处理,这样,当你选择Drawable时,会有抗锯齿的效果
        Rect src = new Rect(0,icon.getWidth(),icon.getHeight());
        Rect dst = new Rect(0,iconSize);
        canvas.drawBitmap(icon,src,dst,iconPaint);

        if(isShowNum){

            if(TextUtils.isEmpty(num)){
                num = "0";
            }

            if(!TextUtils.isDigitsOnly(num)){
                //非数字
                Log.e(TAG,"the num is not digit :"+ num);
                num = "0";
            }

            int numInt = Integer.valueOf(num);

            if(numInt > 99){//超过99

                num = "99+";

                // 启用抗锯齿和使用设备的文本字体大小
                Paint numPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
                numPaint.setColor(Color.WHITE);
                numPaint.setTextSize(20f*factor);
                numPaint.setTypeface(Typeface.DEFAULT_BOLD);
                int textWidth=(int)numPaint.measureText(num,num.length());

                Log.e(TAG,"text width:"+textWidth);

                int circleCenter = (int) (15*factor);//中心坐标
                int circleRadius = (int) (13*factor);//圆的半径

                //绘制左边的圆形
                Paint leftCirPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                leftCirPaint.setColor(Color.RED);
                canvas.drawCircle(iconSize-circleRadius-textWidth+(10*factor),circleCenter,circleRadius,leftCirPaint);

                //绘制右边的圆形
                Paint rightCirPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                rightCirPaint.setColor(Color.RED);
                canvas.drawCircle(iconSize-circleRadius,rightCirPaint);

                //绘制中间的距形
                Paint rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                rectPaint.setColor(Color.RED);
                RectF oval = new RectF(iconSize-circleRadius-textWidth+(10*factor),2*factor,iconSize-circleRadius,circleRadius*2+2*factor);
                canvas.drawRect(oval,rectPaint);

                //绘制数字
                canvas.drawText(num,(float)(iconSize-textWidth/2-(24*factor)),23*factor,numPaint);

            }else{//<=99

                // 启用抗锯齿和使用设备的文本字体大小
                Paint numPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
                numPaint.setColor(Color.WHITE);
                numPaint.setTextSize(20f*factor);
                numPaint.setTypeface(Typeface.DEFAULT_BOLD);
                int textWidth=(int)numPaint.measureText(num,"text width:"+textWidth);

                //绘制外面的圆形
                //Paint outCirPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                //outCirPaint.setColor(Color.WHITE);
                //canvas.drawCircle(iconSize - 15,15,outCirPaint);

                //绘制内部的圆形
                Paint inCirPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                inCirPaint.setColor(Color.RED);
                canvas.drawCircle(iconSize-15*factor,15*factor,inCirPaint);

                //绘制数字
                canvas.drawText(num,(float)(iconSize-textWidth/2-15*factor),22*factor,numPaint);
            }
        }
        return numIcon;
    }

    /***
     * 
     * 生成有数字的图片(没有边框)
     * @param context
     * @param icon 图片
     * @param isShowNum 是否要绘制数字
     * @param num 数字字符串:整型数字 超过99,显示为"99+"
     * @return
     */
    public static Bitmap generatorNumIcon2(Context context,"the num is not digit :"+ num);
                num = "0";
            }

            int numInt = Integer.valueOf(num);

            if(numInt > 99){//超过99
                num = "99+";
            }

            //启用抗锯齿和使用设备的文本字体大小
            //测量文本占用的宽度
            Paint numPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
            numPaint.setColor(Color.WHITE);
            numPaint.setTextSize(20f*factor);
            numPaint.setTypeface(Typeface.DEFAULT_BOLD);
            int textWidth=(int)numPaint.measureText(num,num.length());
            Log.e(TAG,"text width:"+textWidth);

            /**----------------------------------*
             * Todo 绘制圆角矩形背景 start
             *------------------------------------*/
            //圆角矩形背景的宽度
            int backgroundHeight = (int) (2*15*factor);
            int backgroundWidth = textWidth>backgroundHeight ? (int)(textWidth+10*factor) : backgroundHeight;

            canvas.save();//保存状态

            ShapeDrawable drawable = getDefaultBackground(context);
            drawable.setIntrinsicHeight(backgroundHeight);
            drawable.setIntrinsicWidth(backgroundWidth);
            drawable.setBounds(0,backgroundWidth,backgroundHeight);
            canvas.translate(iconSize-backgroundWidth,0);
            drawable.draw(canvas);

            canvas.restore();//重置为之前保存的状态

            /**----------------------------------*
             * Todo 绘制圆角矩形背景 end
             *------------------------------------*/

            //绘制数字
            canvas.drawText(num,(float)(iconSize-(backgroundWidth + textWidth)/2),numPaint);
        }
        return numIcon;
    }
    /***
     * 
     * 生成有数字的图片(有边框)
     * @param context
     * @param icon 图片
     * @param isShowNum 是否要绘制数字
     * @param num 数字字符串:整型数字 超过99,显示为"99+"
     * @return
     */
    public static Bitmap generatorNumIcon3(Context context,"text width:"+textWidth);

            /**----------------------------------*
             * Todo 绘制圆角矩形背景:先画边框,再画内部的圆角矩形 start
             *------------------------------------*/
            //圆角矩形背景的宽度
            int backgroundHeight = (int) (2*15*factor);
            int backgroundWidth = textWidth>backgroundHeight ? (int)(textWidth+10*factor) : backgroundHeight;
            //边框的宽度
            int strokeThickness = (int) (2*factor);

            canvas.save();//保存状态

            int strokeHeight = backgroundHeight + strokeThickness*2;
            int strokeWidth = textWidth>strokeHeight ? (int)(textWidth+ 10*factor + 2*strokeThickness) : strokeHeight;
            ShapeDrawable outstroke = getDefaultstrokeDrawable(context);
            outstroke.setIntrinsicHeight(strokeHeight);
            outstroke.setIntrinsicWidth(strokeWidth);
            outstroke.setBounds(0,strokeWidth,strokeHeight);
            canvas.translate(iconSize-strokeWidth-strokeThickness,strokeThickness);
            outstroke.draw(canvas);

            canvas.restore();//重置为之前保存的状态

            canvas.save();//保存状态

            ShapeDrawable drawable = getDefaultBackground(context);
            drawable.setIntrinsicHeight((int) (backgroundHeight+2*factor));
            drawable.setIntrinsicWidth((int) (backgroundWidth+2*factor));
            drawable.setBounds(0,backgroundHeight);
            canvas.translate(iconSize-backgroundWidth-2*strokeThickness,2*strokeThickness);
            drawable.draw(canvas);

            canvas.restore();//重置为之前保存的状态

            /**----------------------------------*
             * Todo 绘制圆角矩形背景 end
             *------------------------------------*/

            //绘制数字
            canvas.drawText(num,(float)(iconSize-(backgroundWidth + textWidth+4*strokeThickness)/2),(22)*factor+2*strokeThickness,numPaint);
        }
        return numIcon;
    }

    /***
     * 
     * 生成有数字的图片(有边框的)
     * @param context
     * @param icon 图片
     * @param isShowNum 是否要绘制数字
     * @param num 数字字符串:整型数字 超过99,显示为"99+"
     * @return
     */
    public static Bitmap generatorNumIcon4(Context context,Config.ARGB_8888);
        Canvas canvas = new Canvas(numIcon);

        // 拷贝图片
        Paint iconPaint = new Paint();
        iconPaint.setDither(true);// 防抖处理
        iconPaint.setFilterBitmap(true);// 用来对Bitmap进行滤波处理,这样,当你选择Drawable时,会有抗锯齿的效果
        Rect src = new Rect(0,"the num is not digit :"+ num);
                num = "0";
            }

            int numInt = Integer.valueOf(num);

            if(numInt > 99){//超过99
                num = "99+";
            }

            //启用抗锯齿和使用设备的文本字体
            //测量文本占用的宽度
            Paint numPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
            numPaint.setColor(Color.WHITE);
            numPaint.setTextSize(25f*factor);
            numPaint.setTypeface(Typeface.DEFAULT_BOLD);
            int textWidth=(int)numPaint.measureText(num,"text width:"+textWidth);

            /**----------------------------------*
             * Todo 绘制圆角矩形背景 start
             *------------------------------------*/
            //边框的宽度
            int strokeThickness = (int) (DEFAULT_stroke_WIDTH_DIP*factor);
            //圆角矩形背景的宽度
            float radiusPx = 15*factor;
            int backgroundHeight = (int) (2*(radiusPx+strokeThickness));//2*(半径+边框宽度)
            int backgroundWidth = textWidth>backgroundHeight ? (int)(textWidth + 10*factor + 2*strokeThickness) : backgroundHeight;

            canvas.save();//保存状态

            ShapeDrawable drawable = getDefaultBackground2(context);
            drawable.setIntrinsicHeight(backgroundHeight);
            drawable.setIntrinsicWidth(backgroundWidth);
            drawable.setBounds(0,backgroundHeight);
            canvas.translate(iconSize-backgroundWidth-strokeThickness,(float)(iconSize-(backgroundWidth + textWidth+2*strokeThickness)/2),(float) (25*factor+2.5*strokeThickness),numPaint);
        }
        return numIcon;
    }

    /***
     * 创建原生系统的快捷方式
     * @param context 
     * @param clazz 启动的activity
     * @param isShowNum 是否显示数字
     * @param num 显示的数字:整型
     * @param isstroke 是否加上边框
     */

    public static void installrawShortCut(Context context,boolean isstroke) {
        Log.e(TAG,"installShortCut....");

        Intent shortcutIntent = new Intent(    "com.android.launcher.action.INSTALL_SHORTCUT");
        //名称
        shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME,context.getString(R.string.app_name));

        // 是否可以有多个快捷方式的副本,参数如果是true就可以生成多个快捷方式,如果是false就不会重复添加
        shortcutIntent.putExtra("duplicate",false);

        //点击快捷方式:打开activity
        Intent mainIntent = new Intent(Intent.ACTION_MAIN);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        mainIntent.setClass(context,clazz);
        shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT,mainIntent);

        //快捷方式的图标
        if(isstroke){
            shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON,generatorNumIcon4(
                            context,((BitmapDrawable)context.getResources().getDrawable(R.drawable.ic_launcher)).getBitmap(),num));
        }else{
            shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON,generatorNumIcon2(
                            context,num));
        }
        context.sendbroadcast(shortcutIntent);
    }

    /***
     * 是否已经创建了快捷方式
     * @param context
     * @return
     */
    public static boolean isAddShortCut(Context context) {
         Log.e(TAG,"isAddShortCut....");

      boolean isInstallShortcut = false;
      final ContentResolver cr = context.getContentResolver();

      //Todo 注释的代码,在有的手机:修改了ROM的系统,不能支持
      /*int versionLevel = android.os.Build.VERSION.SDK_INT;
            String AUTHORITY = "com.android.launcher2.settings";
            //2.2以上的系统的文件文件名字是不一样的
            if (versionLevel >= 8) {
              AUTHORITY = "com.android.launcher2.settings";
            } else {
              AUTHORITY = "com.android.launcher.settings";
            }*/

        String AUTHORITY = getAuthorityFromPermission(context,"com.android.launcher.permission.READ_SETTINGS");
        Log.e(TAG,"AUTHORITY : " +AUTHORITY);
        final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
        + "/favorites?notify=true");

        Cursor c = cr.query(CONTENT_URI,new String[] { "title" },"title=?",new String[] { context.getString(R.string.app_name) },null);

        if (c != null && c.getCount() > 0) {
          isInstallShortcut = true;
        }

        if(c != null){
            c.close();
        }

        Log.e(TAG,"isAddShortCut....isInstallShortcut="+isInstallShortcut);

      return isInstallShortcut;
    }

    /**
     * 删除快捷方式 
     * @param context
     * @param clazz
     */
     public static void deleteShortCut(Context context,Class<?> clazz){
         Log.e(TAG,"delShortcut....");

         if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")){
            //小米
            //当为""时,不显示数字,相当于隐藏了)
            xiaoMiShortCut(context,"");

        }else if(Build.MANUFACTURER.equalsIgnoreCase("samsung")){
            //三星
            samsungShortCut(context,"0");

        }else {//其他原生系统手机
            //删除显示数字的快捷方式
            deleterawShortCut(context,clazz);
            //安装不显示数字的快捷方式
            //installrawShortCut(context,false,"0");
        }
     }

    /***
     * 删除原生系统的快捷方式
     * @param context
     * @param clazz 启动的activity
     */
    public static void deleterawShortCut(Context context,Class<?> clazz) {
        Intent intent = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
        //快捷方式的名称
        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,context.getString(R.string.app_name));

        Intent intent2 = new Intent(); 
        intent2.setClass(context,clazz); 
        intent2.setAction(Intent.ACTION_MAIN); 
        intent2.addCategory(Intent.CATEGORY_LAUNCHER); 
        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT,intent2); 

        context.sendbroadcast(intent);
    }

     /***
     * 取得权限相应的认证URI
     * @param context
     * @param permission
     * @return
     */
    public static String getAuthorityFromPermission(Context context,String permission) {
        if (TextUtils.isEmpty(permission)) {
            return null;
        }
        List<PackageInfo> packInfos = context.getPackageManager().getInstalledPackages(PackageManager.GET_PROVIDERS);
        if (packInfos == null) {
            return null;
        }
        for (PackageInfo info : packInfos) {
            ProviderInfo[] providers = info.providers;
            if (providers != null) {
                for (ProviderInfo provider : providers) {
                    if (permission.equals(provider.readPermission)
                            || permission.equals(provider.writePermission)) {
                        return provider.authority;
                    }
                }
            }
        }
        return null;
    }

    /***
     * 在小米应用图标的快捷方式上加数字<br>
     * 
     * 
     * @param context
     * @param num 显示的数字:大于99,为"99",当为""时,不显示数字,相当于隐藏了)<br><br>
     * 
     * 注意点:
     * context.getPackageName()+"/."+clazz.getSimpleName() (这个是启动activity的路径)中的"/."不能缺少
     * 
     */
    public static void xiaoMiShortCut(Context context,String num)
     {
        Log.e(TAG,"xiaoMiShortCut....");
        Intent localIntent = new Intent("android.intent.action.APPLICATION_MESSAGE_UPDATE");
        localIntent.putExtra("android.intent.extra.update_application_component_name",context.getPackageName()+"/."+clazz.getSimpleName());
        if(TextUtils.isEmpty(num)){
            num = "";
        }else{
          int numInt = Integer.valueOf(num);
          if (numInt > 0){
             if (numInt > 99){
                num = "99";
           }
          }else{
              num = "0";
          }
        }
        localIntent.putExtra("android.intent.extra.update_application_message_text",num);
        context.sendbroadcast(localIntent);
     }                                            

     /***
     * 索尼手机:应用图标的快捷方式上加数字
     * @param context
     * @param num
     */
    public static void sonyShortCut(Context context,String num)
     {
      String activityName = getLaunchActivityName(context);
      if (activityName == null){
       return;
      }
      Intent localIntent = new Intent();
      int numInt = Integer.valueOf(num);
      boolean isShow = true;
      if (numInt < 1){
       num = "";
       isShow = false;
      }else if (numInt > 99){
          num = "99";
      }
      localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE",isShow);
      localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE");
      localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME",activityName);
      localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE",num);
      localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME",context.getPackageName());
      context.sendbroadcast(localIntent);
     }

     /***
     * 三星手机:应用图标的快捷方式上加数字
     * @param context
     * @param num
     */
    public static void samsungShortCut(Context context,String num)
     {
        int numInt = Integer.valueOf(num);
      if (numInt < 1)
      {
       num = "0";
      }else if (numInt > 99){
          num = "99";
      }
         String activityName = getLaunchActivityName(context);
      Intent localIntent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
      localIntent.putExtra("badge_count",num);
      localIntent.putExtra("badge_count_package_name",context.getPackageName());
      localIntent.putExtra("badge_count_class_name",activityName);
      context.sendbroadcast(localIntent);
     }

     /***
     * 在应用图标的快捷方式上加数字
     * @param clazz 启动的activity
     * @param isShowNum 是否显示数字
     * @param num 显示的数字:整型
     * @param isstroke 是否加上边框
     * 
     */
    public static void addNumShortCut(Context context,isstroke);
      }

     }

     /***
     * 取得当前应用的启动activity的名称:
     * mainfest.xml中配置的 android:name:"
     * @param context
     * @return
     */
    public static String getLaunchActivityName(Context context)
     {
      PackageManager localPackageManager = context.getPackageManager();
      Intent localIntent = new Intent("android.intent.action.MAIN");
      localIntent.addCategory("android.intent.category.LAUNCHER");
      try
      {
       Iterator<ResolveInfo> localIterator = localPackageManager.queryIntentActivities(localIntent,0).iterator();
       while (localIterator.hasNext())
       {
        ResolveInfo localResolveInfo = localIterator.next();
        if (!localResolveInfo.activityInfo.applicationInfo.packageName.equalsIgnoreCase(context.getPackageName()))
         continue;
        String str = localResolveInfo.activityInfo.name;
        return str;
       }
      }
      catch (Exception localException)
      {
       return null;
      }
      return null;
     }

    /***
     * 得到一个默认的背景:圆角矩形<br><br>
     * 使用代码来生成一个背景:相当于用<shape>的xml的背景
     * 
     * @return
     */
    private static ShapeDrawable getDefaultBackground(Context context) {

        //这个是为了应对不同分辨率的手机,屏幕兼容性
        int r = dipToPixels(context,DEFAULT_CORNER_RADIUS_DIP);
        float[] outerR = new float[] {r,r,r};

        //圆角矩形
        RoundRectShape rr = new RoundRectShape(outerR,null,null);
        ShapeDrawable drawable = new ShapeDrawable(rr);
        drawable.getPaint().setColor(DEFAULT_NUM_COLOR);//设置颜色
        return drawable;

    }
    /***
     * 得到一个默认的背景:圆角矩形<br><br>
     * 使用代码来生成一个背景:相当于用<shape>的xml的背景
     * 
     * @return
     */
    private static ShapeDrawable getDefaultBackground2(Context context) {

        //这个是为了应对不同分辨率的手机,屏幕兼容性
        int r = dipToPixels(context,r};
        int distance = dipToPixels(context,DEFAULT_stroke_WIDTH_DIP);

        //圆角矩形
        RoundRectShape rr = new RoundRectShape(outerR,null);
        customBorderDrawable drawable = new customBorderDrawable(context,rr);
        drawable.getFillpaint().setColor(DEFAULT_NUM_COLOR);//设置填充颜色
        drawable.getstrokepaint().setColor(DEFAULT_stroke_COLOR);//设置边框颜色
        drawable.getstrokepaint().setstrokeWidth(distance);//设置边框宽度
        return drawable;

    }

    /***
     * 得到一个默认的背景:圆角矩形<br><br>
     * 使用代码来生成一个背景:相当于用<shape>的xml的背景
     * 
     * @return
     */
    private static ShapeDrawable getDefaultstrokeDrawable(Context context) {

        //这个是为了应对不同分辨率的手机,屏幕兼容性
        int r = dipToPixels(context,DEFAULT_CORNER_RADIUS_DIP);
        int distance = dipToPixels(context,DEFAULT_stroke_WIDTH_DIP);
        float[] outerR = new float[] {r,null);
        ShapeDrawable drawable = new ShapeDrawable(rr);
        drawable.getPaint().setstrokeWidth(distance);
        drawable.getPaint().setStyle(Paint.Style.FILL);
        drawable.getPaint().setColor(DEFAULT_stroke_COLOR);//设置颜色
        return drawable;
    }
    /***
     * dp to px
     * @param dip
     * @return
     */
    public static int dipToPixels(Context context,int dip) {
        Resources r = context.getResources();
        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,r.getdisplayMetrics());
        return (int) px;
    }
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持编程小技巧!

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

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

呜呼:现在国内的风气实在不好,技术群里充斥着广告黄图gif,发个问题出来想讨论一下,根本没人搭理,聊起游戏一大批人跳出来各种装逼。同样都是混子,混得好点的写书出视频糊弄小学生,混得不好的整天在群里没事找事...求知的路上异常坎坷,直到遇见你,我才在雾霾里找到一盏明灯!特转载你的一片美文,方便自己,方便大家,发扬光大!以下是人家写的原文:

大家好,今天给大家带来一个仿 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,加入如下代码:  


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    tools:context=".MainActivity" >  
    <Button  
        android:id="@+id/start_float_window"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="Start Float Window" >  
    </Button>  
</RelativeLayout>

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


<?xml version="1.0" encoding="UTF-8"?>  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/small_window_layout"  
    android:layout_width="60dip"  
    android:layout_height="25dip"  
    android:background="@drawable/bg_small"  
    >  
    <TextView   
        android:id="@+id/percent"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:gravity="center"  
        android:textColor="#ffffff"  
        />  
</LinearLayout>  

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


<?xml version="1.0" encoding="UTF-8"?>  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/big_window_layout"  
    android:layout_width="200dip"  
    android:layout_height="100dip"  
    android:background="@drawable/bg_big"  
    android:orientation="vertical"  
    >  
    <Button   
        android:id="@+id/close"  
        android:layout_width="100dip"  
        android:layout_height="40dip"  
        android:layout_gravity="center_horizontal"  
        android:layout_marginTop="12dip"  
        android:text="关闭悬浮窗"  
        />  
    <Button   
        android:id="@+id/back"  
        android:layout_width="100dip"  
        android:layout_height="40dip"  
        android:layout_gravity="center_horizontal"  
        android:text="返回"  
        />  
</LinearLayout> 

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

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


public class MainActivity extends Activity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Button startFloatWindow = (Button) findViewById(R.id.start_float_window);  
        startFloatWindow.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View arg0) {  
                Intent intent = new Intent(MainActivity.this, FloatWindowService.class);  
                startService(intent);  
                finish();  
            }  
        });  
    }  
}  

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


public class FloatWindowService extends Service {  
  
    /** 
     * 用于在线程中创建或移除悬浮窗。 
     */  
    private Handler handler = new Handler();  
  
    /** 
     * 定时器,定时进行检测当前应该创建还是移除悬浮窗。 
     */  
    private Timer timer;  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        // 开启定时器,每隔0.5秒刷新一次  
        if (timer == null) {  
            timer = new Timer();  
            timer.scheduleAtFixedRate(new RefreshTask(), 0500);  
        }  
        return super.onStartCommand(intent, flags, startId);  
    }  
  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        // Service被终止的同时也停止定时器继续运行  
        timer.cancel();  
        timer = null;  
    }  
  
    class RefreshTask extends TimerTask {  
  
        @Override  
        public void run() {  
            // 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。  
            if (isHome() && !MyWindowManager.isWindowShowing()) {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() {  
                        MyWindowManager.createSmallWindow(getApplicationContext());  
                    }  
                });  
            }  
            // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。  
            else if (!isHome() && MyWindowManager.isWindowShowing()) {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() {  
                        MyWindowManager.removeSmallWindow(getApplicationContext());  
                        MyWindowManager.removeBigWindow(getApplicationContext());  
                    }  
                });  
            }  
            // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。  
            else if (isHome() && MyWindowManager.isWindowShowing()) {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() {  
                        MyWindowManager.updateUsedPercent(getApplicationContext());  
                    }  
                });  
            }  
        }  
  
    }  
  
    /** 
     * 判断当前界面是否是桌面 
     */  
    private boolean isHome() {  
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);  
        List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);  
        return getHomes().contains(rti.get(0).topActivity.getPackageName());  
    }  
  
    /** 
     * 获得属于桌面的应用的应用包名称 
     *  
     * @return 返回包含所有包名的字符串列表 
     */  
    private List<String> getHomes() {  
        List<String> names = new ArrayList<String>();  
        PackageManager packageManager = this.getPackageManager();  
        Intent intent = new Intent(Intent.ACTION_MAIN);  
        intent.addCategory(Intent.CATEGORY_HOME);  
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,  
                PackageManager.MATCH_DEFAULT_ONLY);  
        for (ResolveInfo ri : resolveInfo) {  
            names.add(ri.activityInfo.packageName);  
        }  
        return names;  
    }  
}  

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

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

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

在 FloatWindowSmallView 中加入如下代码:


public class FloatWindowSmallView extends LinearLayout {  
  
    /** 
     * 记录小悬浮窗的宽度 
     */  
    public static int viewWidth;  
  
    /** 
     * 记录小悬浮窗的高度 
     */  
    public static int viewHeight;  
  
    /** 
     * 记录系统状态栏的高度 
     */  
     private static int statusBarHeight;  
  
    /** 
     * 用于更新小悬浮窗的位置 
     */  
    private WindowManager windowManager;  
  
    /** 
     * 小悬浮窗的参数 
     */  
    private WindowManager.LayoutParams mParams;  
  
    /** 
     * 记录当前手指位置在屏幕上的横坐标值 
     */  
    private float xInScreen;  
  
    /** 
     * 记录当前手指位置在屏幕上的纵坐标值 
     */  
    private float yInScreen;  
  
    /** 
     * 记录手指按下时在屏幕上的横坐标的值 
     */  
    private float xDownInScreen;  
  
    /** 
     * 记录手指按下时在屏幕上的纵坐标的值 
     */  
    private float yDownInScreen;  
  
    /** 
     * 记录手指按下时在小悬浮窗的View上的横坐标的值 
     */  
    private float xInView;  
  
    /** 
     * 记录手指按下时在小悬浮窗的View上的纵坐标的值 
     */  
    private float yInView;  
  
    public FloatWindowSmallView(Context context) {  
        super(context);  
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
        LayoutInflater.from(context).inflate(R.layout.float_window_small, this);  
        View view = findViewById(R.id.small_window_layout);  
        viewWidth = view.getLayoutParams().width;  
        viewHeight = view.getLayoutParams().height;  
        TextView percentView = (TextView) findViewById(R.id.percent);  
        percentView.setText(MyWindowManager.getUsedPercentValue(context));  
    }  
  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        switch (event.getAction()) {  
        case MotionEvent.ACTION_DOWN:  
            // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度  
            xInView = event.getX();  
            yInView = event.getY();  
            xDownInScreen = event.getRawX();  
            yDownInScreen = event.getRawY() - getStatusBarHeight();  
            xInScreen = event.getRawX();  
            yInScreen = event.getRawY() - getStatusBarHeight();  
            break;  
        case MotionEvent.ACTION_MOVE:  
            xInScreen = event.getRawX();  
            yInScreen = event.getRawY() - getStatusBarHeight();  
            // 手指移动的时候更新小悬浮窗的位置  
            updateViewPosition();  
            break;  
        case MotionEvent.ACTION_UP:  
            // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。  
            if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {  
                openBigWindow();  
            }  
            break;  
        default:  
            break;  
        }  
        return true;  
    }  
  
    /** 
     * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 
     *  
     * @param params 
     *            小悬浮窗的参数 
     */  
    public void setParams(WindowManager.LayoutParams params) {  
        mParams = params;  
    }  
  
    /** 
     * 更新小悬浮窗在屏幕中的位置。 
     */  
    private void updateViewPosition() {  
        mParams.x = (int) (xInScreen - xInView);  
        mParams.y = (int) (yInScreen - yInView);  
        windowManager.updateViewLayout(this, mParams);  
    }  
  
    /** 
     * 打开大悬浮窗,同时关闭小悬浮窗。 
     */  
    private void openBigWindow() {  
        MyWindowManager.createBigWindow(getContext());  
        MyWindowManager.removeSmallWindow(getContext());  
    }  
  
    /** 
     * 用于获取状态栏的高度。 
     *  
     * @return 返回状态栏高度的像素值。 
     */  
    private int getStatusBarHeight() {  
        if (statusBarHeight == 0) {  
            try {  
                Class<?> c = Class.forName("com.android.internal.R$dimen");  
                Object o = c.newInstance();  
                Field field = c.getField("status_bar_height");  
                int x = (Integer) field.get(o);  
                statusBarHeight = getResources().getDimensionPixelSize(x);  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
        return statusBarHeight;  
    }  

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

在 FloatWindowBigView 中加入如下代码:


public class FloatWindowBigView extends LinearLayout {  
  
    /** 
     * 记录大悬浮窗的宽度 
     */  
    public static int viewWidth;  
  
    /** 
     * 记录大悬浮窗的高度 
     */  
    public static int viewHeight;  
  
    public FloatWindowBigView(final Context context) {  
        super(context);  
        LayoutInflater.from(context).inflate(R.layout.float_window_big, this);  
        View view = findViewById(R.id.big_window_layout);  
        viewWidth = view.getLayoutParams().width;  
        viewHeight = view.getLayoutParams().height;  
        Button close = (Button) findViewById(R.id.close);  
        Button back = (Button) findViewById(R.id.back);  
        close.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                // 点击关闭悬浮窗的时候,移除所有悬浮窗,并停止Service  
                MyWindowManager.removeBigWindow(context);  
                MyWindowManager.removeSmallWindow(context);  
                Intent intent = new Intent(getContext(), FloatWindowService.class);  
                context.stopService(intent);  
            }  
        });  
        back.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                // 点击返回的时候,移除大悬浮窗,创建小悬浮窗  
                MyWindowManager.removeBigWindow(context);  
                MyWindowManager.createSmallWindow(context);  
            }  
        });  
    }  
}  

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

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


public class MyWindowManager {  
  
    /** 
     * 小悬浮窗View的实例 
     */  
    private static FloatWindowSmallView smallWindow;  
  
    /** 
     * 大悬浮窗View的实例 
     */  
    private static FloatWindowBigView bigWindow;  
  
    /** 
     * 小悬浮窗View的参数 
     */  
    private static LayoutParams smallWindowParams;  
  
    /** 
     * 大悬浮窗View的参数 
     */  
    private static LayoutParams bigWindowParams;  
  
    /** 
     * 用于控制在屏幕上添加或移除悬浮窗 
     */  
    private static WindowManager mWindowManager;  
  
    /** 
     * 用于获取手机可用内存 
     */  
    private static ActivityManager mActivityManager;  
  
    /** 
     * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。 
     *  
     * @param context 
     *            必须为应用程序的Context. 
     */  
    public static void createSmallWindow(Context context) {  
        WindowManager windowManager = getWindowManager(context);  
        int screenWidth = windowManager.getDefaultDisplay().getWidth();  
        int screenHeight = windowManager.getDefaultDisplay().getHeight();  
        if (smallWindow == null) {  
            smallWindow = new FloatWindowSmallView(context);  
            if (smallWindowParams == null) {  
                smallWindowParams = new LayoutParams();  
                smallWindowParams.type = LayoutParams.TYPE_PHONE;  
                smallWindowParams.format = PixelFormat.RGBA_8888;  
                smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL  
                        | LayoutParams.FLAG_NOT_FOCUSABLE;  
                smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
                smallWindowParams.width = FloatWindowSmallView.viewWidth;  
                smallWindowParams.height = FloatWindowSmallView.viewHeight;  
                smallWindowParams.x = screenWidth;  
                smallWindowParams.y = screenHeight / 2;  
            }  
            smallWindow.setParams(smallWindowParams);  
            windowManager.addView(smallWindow, smallWindowParams);  
        }  
    }  
  
    /** 
     * 将小悬浮窗从屏幕上移除。 
     *  
     * @param context 
     *            必须为应用程序的Context. 
     */  
    public static void removeSmallWindow(Context context) {  
        if (smallWindow != null) {  
            WindowManager windowManager = getWindowManager(context);  
            windowManager.removeView(smallWindow);  
            smallWindow = null;  
        }  
    }  
  
    /** 
     * 创建一个大悬浮窗。位置为屏幕正中间。 
     *  
     * @param context 
     *            必须为应用程序的Context. 
     */  
    public static void createBigWindow(Context context) {  
        WindowManager windowManager = getWindowManager(context);  
        int screenWidth = windowManager.getDefaultDisplay().getWidth();  
        int screenHeight = windowManager.getDefaultDisplay().getHeight();  
        if (bigWindow == null) {  
            bigWindow = new FloatWindowBigView(context);  
            if (bigWindowParams == null) {  
                bigWindowParams = new LayoutParams();  
                bigWindowParams.x = screenWidth / 2 - FloatWindowBigView.viewWidth / 2;  
                bigWindowParams.y = screenHeight / 2 - FloatWindowBigView.viewHeight / 2;  
                bigWindowParams.type = LayoutParams.TYPE_PHONE;  
                bigWindowParams.format = PixelFormat.RGBA_8888;  
                bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;  
                bigWindowParams.width = FloatWindowBigView.viewWidth;  
                bigWindowParams.height = FloatWindowBigView.viewHeight;  
            }  
            windowManager.addView(bigWindow, bigWindowParams);  
        }  
    }  
  
    /** 
     * 将大悬浮窗从屏幕上移除。 
     *  
     * @param context 
     *            必须为应用程序的Context. 
     */  
    public static void removeBigWindow(Context context) {  
        if (bigWindow != null) {  
            WindowManager windowManager = getWindowManager(context);  
            windowManager.removeView(bigWindow);  
            bigWindow = null;  
        }  
    }  
  
    /** 
     * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。 
     *  
     * @param context 
     *            可传入应用程序上下文。 
     */  
    public static void updateUsedPercent(Context context) {  
        if (smallWindow != null) {  
            TextView percentView = (TextView) smallWindow.findViewById(R.id.percent);  
            percentView.setText(getUsedPercentValue(context));  
        }  
    }  
  
    /** 
     * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。 
     *  
     * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。 
     */  
    public static boolean isWindowShowing() {  
        return smallWindow != null || bigWindow != null;  
    }  
  
    /** 
     * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。 
     *  
     * @param context 
     *            必须为应用程序的Context. 
     * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。 
     */  
    private static WindowManager getWindowManager(Context context) {  
        if (mWindowManager == null) {  
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
        }  
        return mWindowManager;  
    }  
  
    /** 
     * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。 
     *  
     * @param context 
     *            可传入应用程序上下文。 
     * @return ActivityManager的实例,用于获取手机可用内存。 
     */  
    private static ActivityManager getActivityManager(Context context) {  
        if (mActivityManager == null) {  
            mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);  
        }  
        return mActivityManager;  
    }  
  
    /** 
     * 计算已使用内存的百分比,并返回。 
     *  
     * @param context 
     *            可传入应用程序上下文。 
     * @return 已使用内存的百分比,以字符串形式返回。 
     */  
    public static String getUsedPercentValue(Context context) {  
        String dir = "/proc/meminfo";  
        try {  
            FileReader fr = new FileReader(dir);  
            BufferedReader br = new BufferedReader(fr, 2048);  
            String memoryLine = br.readLine();  
            String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));  
            br.close();  
            long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+"""));  
            long availableSize = getAvailableMemory(context) / 1024;  
            int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);  
            return percent + "%";  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return "悬浮窗";  
    }  
  
    /** 
     * 获取当前可用内存,返回数据以字节为单位。 
     *  
     * @param context 
     *            可传入应用程序上下文。 
     * @return 当前可用内存。 
     */  
    private static long getAvailableMemory(Context context) {  
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();  
        getActivityManager(context).getMemoryInfo(mi);  
        return mi.availMem;  
    }  
  
}  

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

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


<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.demo.floatwindowdemo"  
    android:versionCode="1"  
    android:versionName="1.0" >  
  
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />  
  
    <uses-sdk  
        android:minSdkVersion="8"  
        android:targetSdkVersion="8" />  
  
    <application  
        android:allowBackup="true"  
        android:icon="@drawable/ic_launcher"  
        android:label="@string/app_name"  
        android:theme="@style/AppTheme" >  
        <activity  
            android:name="com.demo.floatwindowdemo.MainActivity"  
            android:label="@string/app_name" >  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
          
        <service android:name=".FloatWindowService"></service>  
    </application>  
  
</manifest> 

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

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

                     

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

                   

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

                                            

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

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

源码下载,请点击这里

补充:

有朋友跟我反应,上面的代码在 Android 3.0 以上的系统运行会崩溃,我看了一下,确实如此,主要是 3.0 之后想要获取正在运行的任务,需要加上权限声明。在 AndroidManifest.xml 中加入 

<uses-permission android:name="android.permission.GET_TASKS" />

 即可解决此问题。

Android仿360悬浮小球自定义view实现示例

Android仿360悬浮小球自定义view实现示例

Android仿360悬浮小球自定义view实现示例

效果图如下:



实现当前这种类似的效果 和360小球 悬浮桌面差不错类似。这种效果是如何实现的呢。废话不多说 ,直接上代码。

1.新建工程,添加悬浮窗权限。

<uses-permission android:name="android.permission.SYstem_ALERT_WINDOW" />

2.自定义一个FloatMessagerMainWindow

import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;

import com.android.view.FloatMessagePopleDialog;

/**
 * Created by liupanpan on 2017/3/16.
 */

public class FloatMessagerMainWindow {
 private Context context;
 private View view;
 private WindowManager.LayoutParams mParams = null;
 private WindowManager windowManager = null;
 private static FloatMessagerMainWindow floatMessagerMainWindow;

 public FloatMessagerMainWindow(Context context,View view) {
  this.context = context;
  this.view = view;
  showWindow(context);
 }

 public static FloatMessagerMainWindow getFloatMessagerMainWindow(Context context,View view) {
  if (floatMessagerMainWindow == null) {
   synchronized (FloatMessagerMainWindow.class) {
    if (floatMessagerMainWindow == null) {
     floatMessagerMainWindow = new FloatMessagerMainWindow(context,view);
    }
   }
  }
  return floatMessagerMainWindow;
 }

 private void showWindow(final Context context) {
//  if (!isWindowdismiss) {
//   Log.e(TAG,"view is already added here");
//   return;
//  }
//  isWindowdismiss = false;
  if (windowManager == null) {
   windowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
  }

  Point size = new Point();
  windowManager.getDefaultdisplay().getSize(size);
  int screenWidth = size.x;
  int screenHeight = size.y;

  mParams = new WindowManager.LayoutParams();
  mParams.packageName = context.getPackageName();
  mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
  mParams.type = WindowManager.LayoutParams.TYPE_SYstem_ERROR;
//  mParams.softInputMode = WindowManager.LayoutParams.soFT_INPUT_ADJUST_RESIZE |
//    WindowManager.LayoutParams.soFT_INPUT_STATE_HIDDEN;
  mParams.format = PixelFormat.RGBA_8888;
  mParams.gravity = Gravity.LEFT | Gravity.TOP;
  mParams.x = screenWidth - dp2px(context,450);
  mParams.y = screenHeight - dp2px(context,550);


  ImageView imageView = new ImageView(context);
  imageView.setimageResource(R.mipmap.icon_tab_item_message_pressed);
  imageView.setonClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    Toast.makeText(context,"image=========",Toast.LENGTH_SHORT).show();
    View view = LayoutInflater.from(context).inflate(R.layout.float_pople_room_layout,null);
    FloatMessagePopleDialog.getInstance(context,R.style.webviewTheme).setContextView(view);
   }
  });
//  floatView = new AVCallFloatView(context);
//  floatView.setParams(mParams);
//  floatView.setIsShowing(true);
  windowManager.addView(imageView,mParams);
 }

 private int dp2px(Context context,float dp) {
  final float scale = context.getResources().getdisplayMetrics().density;
  return (int) (dp * scale + 0.1f);
 }
}

调用方法:

FloatMessagerMainWindow.getFloatMessagerMainWindow(context,null);

实现到此 ,点击按钮就可以实现 悬浮窗。(此处可能会出现相应的崩溃,崩溃原因是悬浮窗的 悬浮权限开启问题。)

4.我以官方模拟器为例开启悬浮权限:

打开允许在其他应用上的管理权限

此时再次打开工程,点击按钮,就可以实现悬浮效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

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

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

加入如下代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 xmlns:tools="http://schemas.android.com/tools" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 tools:context=".MainActivity" > 
 <Button 
 android:id="@+id/start_float_window" 
 android:layout_width="fill_parent" 
 android:layout_height="wrap_content" 
 android:text="Start Float Window" > 
 </Button> 
</RelativeLayout> 

然后再新建一个名为float_window_small.xml的布局文件,用于做为小悬浮窗的布局,

在其中加入如下代码:

<?xml version="1.0" encoding="UTF-8"?> 
<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/small_window_layout" 
 android:layout_width="60dip" 
 android:layout_height="25dip" 
 android:background="@drawable/bg_small" 
 > 
 <TextView 
 android:id="@+id/percent" 
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent" 
 android:gravity="center" 
 android:textColor="#ffffff" 
 /> 
</LinearLayout> 

再新建一个名为float_window_big.xml的布局文件,用于做为大悬浮窗的布局,

在其中加入如下代码:

<?xml version="1.0" encoding="UTF-8"?> 
<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/big_window_layout" 
 android:layout_width="200dip" 
 android:layout_height="100dip" 
 android:background="@drawable/bg_big" 
 android:orientation="vertical" 
 > 
 <Button 
 android:id="@+id/close" 
 android:layout_width="100dip" 
 android:layout_height="40dip" 
 android:layout_gravity="center_horizontal" 
 android:layout_marginTop="12dip" 
 android:text="关闭悬浮窗" 
 /> 
 <Button 
 android:id="@+id/back" 
 android:layout_width="100dip" 
 android:layout_height="40dip" 
 android:layout_gravity="center_horizontal" 
 android:text="返回" 
 /> 
</LinearLayout> 

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

然后打开或创建MainActivity,这是项目的主界面

在里面加入如下代码:

public class MainActivity extends Activity { 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_main); 
 Button startFloatwindow = (Button) findViewById(R.id.start_float_window); 
 startFloatwindow.setonClickListener(new OnClickListener() { 
 @Override 
 public void onClick(View arg0) { 
 Intent intent = new Intent(MainActivity.this,FloatwindowService.class); 
 startService(intent); 
 finish(); 
 } 
 }); 
 } 
}

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

在里面加入如下代码:

public class FloatwindowService extends Service { 
 
 /** 
 * 用于在线程中创建或移除悬浮窗。 
 */ 
 private Handler handler = new Handler(); 
 
 /** 
 * 定时器,定时进行检测当前应该创建还是移除悬浮窗。 
 */ 
 private Timer timer; 
 
 @Override 
 public IBinder onBind(Intent intent) { 
 return null; 
 } 
 
 @Override 
 public int onStartCommand(Intent intent,int flags,int startId) { 
 // 开启定时器,每隔0.5秒刷新一次 
 if (timer == null) { 
 timer = new Timer(); 
 timer.scheduleAtFixedrate(new RefreshTask(),500); 
 } 
 return super.onStartCommand(intent,flags,startId); 
 } 
 
 @Override 
 public void onDestroy() { 
 super.onDestroy(); 
 // Service被终止的同时也停止定时器继续运行 
 timer.cancel(); 
 timer = null; 
 } 
 
 class RefreshTask extends TimerTask { 
 
 @Override 
 public void run() { 
 // 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。 
 if (isHome() && !MyWindowManager.isWindowShowing()) { 
 handler.post(new Runnable() { 
 @Override 
 public void run() { 
 MyWindowManager.createSmallWindow(getApplicationContext()); 
 } 
 }); 
 } 
 // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。 
 else if (!isHome() && MyWindowManager.isWindowShowing()) { 
 handler.post(new Runnable() { 
 @Override 
 public void run() { 
 MyWindowManager.removeSmallWindow(getApplicationContext()); 
 MyWindowManager.removeBigWindow(getApplicationContext()); 
 } 
 }); 
 } 
 // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。 
 else if (isHome() && MyWindowManager.isWindowShowing()) { 
 handler.post(new Runnable() { 
 @Override 
 public void run() { 
 MyWindowManager.updateUsedPercent(getApplicationContext()); 
 } 
 }); 
 } 
 } 
 
 } 
 
 /** 
 * 判断当前界面是否是桌面 
 */ 
 private boolean isHome() { 
 ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 
 List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1); 
 return getHomes().contains(rti.get(0).topActivity.getPackageName()); 
 } 
 
 /** 
 * 获得属于桌面的应用的应用包名称 
 * 
 * @return 返回包含所有包名的字符串列表 
 */ 
 private List<String> getHomes() { 
 List<String> names = new ArrayList<String>(); 
 PackageManager packageManager = this.getPackageManager(); 
 Intent intent = new Intent(Intent.ACTION_MAIN); 
 intent.addCategory(Intent.CATEGORY_HOME); 
 List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY); 
 for (ResolveInfo ri : resolveInfo) { 
 names.add(ri.activityInfo.packageName); 
 } 
 return names; 
 } 
} 

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

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

新建一个名叫FloatwindowSmallView的类,继承自LinearLayout。新建一个名叫FloatwindowBigView的类,也继承自LinearLayout。

在FloatwindowSmallView中加入如下代码:

public class FloatwindowSmallView extends LinearLayout { 
 
 /** 
 * 记录小悬浮窗的宽度 
 */ 
 public static int viewWidth; 
 
 /** 
 * 记录小悬浮窗的高度 
 */ 
 public static int viewHeight; 
 
 /** 
 * 记录系统状态栏的高度 
 */ 
 private static int statusBarHeight; 
 
 /** 
 * 用于更新小悬浮窗的位置 
 */ 
 private WindowManager windowManager; 
 
 /** 
 * 小悬浮窗的参数 
 */ 
 private WindowManager.LayoutParams mParams; 
 
 /** 
 * 记录当前手指位置在屏幕上的横坐标值 
 */ 
 private float xInScreen; 
 
 /** 
 * 记录当前手指位置在屏幕上的纵坐标值 
 */ 
 private float yInScreen; 
 
 /** 
 * 记录手指按下时在屏幕上的横坐标的值 
 */ 
 private float xDownInScreen; 
 
 /** 
 * 记录手指按下时在屏幕上的纵坐标的值 
 */ 
 private float yDownInScreen; 
 
 /** 
 * 记录手指按下时在小悬浮窗的View上的横坐标的值 
 */ 
 private float xInView; 
 
 /** 
 * 记录手指按下时在小悬浮窗的View上的纵坐标的值 
 */ 
 private float yInView; 
 
 public FloatwindowSmallView(Context context) { 
 super(context); 
 windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 
 LayoutInflater.from(context).inflate(R.layout.float_window_small,this); 
 View view = findViewById(R.id.small_window_layout); 
 viewWidth = view.getLayoutParams().width; 
 viewHeight = view.getLayoutParams().height; 
 TextView percentView = (TextView) findViewById(R.id.percent); 
 percentView.setText(MyWindowManager.getUsedPercentValue(context)); 
 } 
 
 @Override 
 public boolean onTouchEvent(MotionEvent event) { 
 switch (event.getAction()) { 
 case MotionEvent.ACTION_DOWN: 
 // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度 
 xInView = event.getX(); 
 yInView = event.getY(); 
 xDownInScreen = event.getRawX(); 
 yDownInScreen = event.getRawY() - getStatusBarHeight(); 
 xInScreen = event.getRawX(); 
 yInScreen = event.getRawY() - getStatusBarHeight(); 
 break; 
 case MotionEvent.ACTION_MOVE: 
 xInScreen = event.getRawX(); 
 yInScreen = event.getRawY() - getStatusBarHeight(); 
 // 手指移动的时候更新小悬浮窗的位置 
 updateViewPosition(); 
 break; 
 case MotionEvent.ACTION_UP: 
 // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。 
 if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) { 
 openBigWindow(); 
 } 
 break; 
 default: 
 break; 
 } 
 return true; 
 } 
 
 /** 
 * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。 
 * 
 * @param params 
 * 小悬浮窗的参数 
 */ 
 public void setParams(WindowManager.LayoutParams params) { 
 mParams = params; 
 } 
 
 /** 
 * 更新小悬浮窗在屏幕中的位置。 
 */ 
 private void updateViewPosition() { 
 mParams.x = (int) (xInScreen - xInView); 
 mParams.y = (int) (yInScreen - yInView); 
 windowManager.updateViewLayout(this,mParams); 
 } 
 
 /** 
 * 打开大悬浮窗,同时关闭小悬浮窗。 
 */ 
 private void openBigWindow() { 
 MyWindowManager.createBigWindow(getContext()); 
 MyWindowManager.removeSmallWindow(getContext()); 
 } 
 
 /** 
 * 用于获取状态栏的高度。 
 * 
 * @return 返回状态栏高度的像素值。 
 */ 
 private int getStatusBarHeight() { 
 if (statusBarHeight == 0) { 
 try { 
 Class<?> c = Class.forName("com.android.internal.R$dimen"); 
 Object o = c.newInstance(); 
 Field field = c.getField("status_bar_height"); 
 int x = (Integer) field.get(o); 
 statusBarHeight = getResources().getDimensionPixelSize(x); 
 } catch (Exception e) { 
 e.printstacktrace(); 
 } 
 } 
 return statusBarHeight; 
 } 

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

在FloatwindowBigView中加入如下代码:

public class FloatwindowBigView extends LinearLayout { 
 
 /** 
 * 记录大悬浮窗的宽度 
 */ 
 public static int viewWidth; 
 
 /** 
 * 记录大悬浮窗的高度 
 */ 
 public static int viewHeight; 
 
 public FloatwindowBigView(final Context context) { 
 super(context); 
 LayoutInflater.from(context).inflate(R.layout.float_window_big,this); 
 View view = findViewById(R.id.big_window_layout); 
 viewWidth = view.getLayoutParams().width; 
 viewHeight = view.getLayoutParams().height; 
 Button close = (Button) findViewById(R.id.close); 
 Button back = (Button) findViewById(R.id.back); 
 close.setonClickListener(new OnClickListener() { 
 @Override 
 public void onClick(View v) { 
 // 点击关闭悬浮窗的时候,移除所有悬浮窗,并停止Service 
 MyWindowManager.removeBigWindow(context); 
 MyWindowManager.removeSmallWindow(context); 
 Intent intent = new Intent(getContext(),FloatwindowService.class); 
 context.stopService(intent); 
 } 
 }); 
 back.setonClickListener(new OnClickListener() { 
 @Override 
 public void onClick(View v) { 
 // 点击返回的时候,移除大悬浮窗,创建小悬浮窗 
 MyWindowManager.removeBigWindow(context); 
 MyWindowManager.createSmallWindow(context); 
 } 
 }); 
 } 
}

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

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

代码如下:

public class MyWindowManager { 
 
 /** 
 * 小悬浮窗View的实例 
 */ 
 private static FloatwindowSmallView smallWindow; 
 
 /** 
 * 大悬浮窗View的实例 
 */ 
 private static FloatwindowBigView bigWindow; 
 
 /** 
 * 小悬浮窗View的参数 
 */ 
 private static LayoutParams smallWindowParams; 
 
 /** 
 * 大悬浮窗View的参数 
 */ 
 private static LayoutParams bigWindowParams; 
 
 /** 
 * 用于控制在屏幕上添加或移除悬浮窗 
 */ 
 private static WindowManager mWindowManager; 
 
 /** 
 * 用于获取手机可用内存 
 */ 
 private static ActivityManager mActivityManager; 
 
 /** 
 * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。 
 * 
 * @param context 
 * 必须为应用程序的Context. 
 */ 
 public static void createSmallWindow(Context context) { 
 WindowManager windowManager = getwindowManager(context); 
 int screenWidth = windowManager.getDefaultdisplay().getWidth(); 
 int screenHeight = windowManager.getDefaultdisplay().getHeight(); 
 if (smallWindow == null) { 
 smallWindow = new FloatwindowSmallView(context); 
 if (smallWindowParams == null) { 
 smallWindowParams = new LayoutParams(); 
 smallWindowParams.type = LayoutParams.TYPE_PHONE; 
 smallWindowParams.format = PixelFormat.RGBA_8888; 
 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 
 | LayoutParams.FLAG_NOT_FOCUSABLE; 
 smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP; 
 smallWindowParams.width = FloatwindowSmallView.viewWidth; 
 smallWindowParams.height = FloatwindowSmallView.viewHeight; 
 smallWindowParams.x = screenWidth; 
 smallWindowParams.y = screenHeight / 2; 
 } 
 smallWindow.setParams(smallWindowParams); 
 windowManager.addView(smallWindow,smallWindowParams); 
 } 
 } 
 
 /** 
 * 将小悬浮窗从屏幕上移除。 
 * 
 * @param context 
 * 必须为应用程序的Context. 
 */ 
 public static void removeSmallWindow(Context context) { 
 if (smallWindow != null) { 
 WindowManager windowManager = getwindowManager(context); 
 windowManager.removeView(smallWindow); 
 smallWindow = null; 
 } 
 } 
 
 /** 
 * 创建一个大悬浮窗。位置为屏幕正中间。 
 * 
 * @param context 
 * 必须为应用程序的Context. 
 */ 
 public static void createBigWindow(Context context) { 
 WindowManager windowManager = getwindowManager(context); 
 int screenWidth = windowManager.getDefaultdisplay().getWidth(); 
 int screenHeight = windowManager.getDefaultdisplay().getHeight(); 
 if (bigWindow == null) { 
 bigWindow = new FloatwindowBigView(context); 
 if (bigWindowParams == null) { 
 bigWindowParams = new LayoutParams(); 
 bigWindowParams.x = screenWidth / 2 - FloatwindowBigView.viewWidth / 2; 
 bigWindowParams.y = screenHeight / 2 - FloatwindowBigView.viewHeight / 2; 
 bigWindowParams.type = LayoutParams.TYPE_PHONE; 
 bigWindowParams.format = PixelFormat.RGBA_8888; 
 bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP; 
 bigWindowParams.width = FloatwindowBigView.viewWidth; 
 bigWindowParams.height = FloatwindowBigView.viewHeight; 
 } 
 windowManager.addView(bigWindow,bigWindowParams); 
 } 
 } 
 
 /** 
 * 将大悬浮窗从屏幕上移除。 
 * 
 * @param context 
 * 必须为应用程序的Context. 
 */ 
 public static void removeBigWindow(Context context) { 
 if (bigWindow != null) { 
 WindowManager windowManager = getwindowManager(context); 
 windowManager.removeView(bigWindow); 
 bigWindow = null; 
 } 
 } 
 
 /** 
 * 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。 
 * 
 * @param context 
 * 可传入应用程序上下文。 
 */ 
 public static void updateUsedPercent(Context context) { 
 if (smallWindow != null) { 
 TextView percentView = (TextView) smallWindow.findViewById(R.id.percent); 
 percentView.setText(getUsedPercentValue(context)); 
 } 
 } 
 
 /** 
 * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。 
 * 
 * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。 
 */ 
 public static boolean isWindowShowing() { 
 return smallWindow != null || bigWindow != null; 
 } 
 
 /** 
 * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。 
 * 
 * @param context 
 * 必须为应用程序的Context. 
 * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。 
 */ 
 private static WindowManager getwindowManager(Context context) { 
 if (mWindowManager == null) { 
 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 
 } 
 return mWindowManager; 
 } 
 
 /** 
 * 如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。 
 * 
 * @param context 
 * 可传入应用程序上下文。 
 * @return ActivityManager的实例,用于获取手机可用内存。 
 */ 
 private static ActivityManager getActivityManager(Context context) { 
 if (mActivityManager == null) { 
 mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 
 } 
 return mActivityManager; 
 } 
 
 /** 
 * 计算已使用内存的百分比,并返回。 
 * 
 * @param context 
 * 可传入应用程序上下文。 
 * @return 已使用内存的百分比,以字符串形式返回。 
 */ 
 public static String getUsedPercentValue(Context context) { 
 String dir = "/proc/meminfo"; 
 try { 
 FileReader fr = new FileReader(dir); 
 BufferedReader br = new BufferedReader(fr,2048); 
 String memoryLine = br.readLine(); 
 String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:")); 
 br.close(); 
 long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+","")); 
 long availableSize = getAvailableMemory(context) / 1024; 
 int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100); 
 return percent + "%"; 
 } catch (IOException e) { 
 e.printstacktrace(); 
 } 
 return "悬浮窗"; 
 } 
 
 /** 
 * 获取当前可用内存,返回数据以字节为单位。 
 * 
 * @param context 
 * 可传入应用程序上下文。 
 * @return 当前可用内存。 
 */ 
 private static long getAvailableMemory(Context context) { 
 ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); 
 getActivityManager(context).getMemoryInfo(mi); 
 return mi.availMem; 
 } 
 
} 

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

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

里面代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
 package="com.demo.floatwindowdemo" 
 android:versionCode="1" 
 android:versionName="1.0" > 
 
 <uses-permission android:name="android.permission.SYstem_ALERT_WINDOW" /> 
 
 <uses-sdk 
 android:minSdkVersion="8" 
 android:targetSdkVersion="8" /> 
 
 <application 
 android:allowBackup="true" 
 android:icon="@drawable/ic_launcher" 
 android:label="@string/app_name" 
 android:theme="@style/AppTheme" > 
 <activity 
 android:name="com.demo.floatwindowdemo.MainActivity" 
 android:label="@string/app_name" > 
 <intent-filter> 
 <action android:name="android.intent.action.MAIN" /> 
 
 <category android:name="android.intent.category.LAUNCHER" /> 
 </intent-filter> 
 </activity> 
 
 <service android:name=".FloatwindowService"></service> 
 </application> 
 
</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" />

即可解决此问题。

您可能感兴趣的文章:

  • Android项目仿UC浏览器和360手机卫士消息常驻栏(通知栏)
  • Android实现类似360,QQ管家那样的悬浮窗
  • Android仿360悬浮小球自定义view实现示例
  • Android实现仿360桌面悬浮清理内存
  • android仿360加速球实现内存释放
  • Android开发实现模仿360二维码扫描功能实例详解
  • Android静默安装实现方案 仿360手机助手秒装和智能安装功能
  • Android实现360手机助手底部的动画菜单
  • Android菜单(动画菜单、360波纹菜单)
  • Android仿360手机卫士的波浪球形进度条View的源码

今天关于Android实现桌面悬浮小火箭效果android实现桌面悬浮小火箭效果怎么设置的讲解已经结束,谢谢您的阅读,如果想了解更多关于Android 实现桌面未读角标、Android 桌面悬浮窗效果实现,仿 360 手机卫士悬浮窗效果、Android仿360悬浮小球自定义view实现示例、Android仿360桌面手机卫士悬浮窗效果的相关知识,请在本站搜索。

本文标签: