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

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

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

<?xmlversion="1.0"encoding="UTF-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><ImageViewandroid:id="@+id/launcher_img"android:layout_width="200dp"android:layout_height="88dp"android:src="@drawable/launcher_bg_hold"/></LinearLayout>

可以看到,这里的ImageView是用于显示当前火箭发射台状态的。我事先准备好了两张图片,一张是当小火箭未拖动到火箭发射台时显示的,一张是当小火箭拖动到火箭发射台上时显示的。

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

publicclassRocketLauncherextendsLinearLayout{/***记录火箭发射台的宽度*/publicstaticintwidth;/***记录火箭发射台的高度*/publicstaticintheight;/***火箭发射台的背景图片*/privateImageViewlauncherImg;publicRocketLauncher(Contextcontext){super(context);LayoutInflater.from(context).inflate(R.layout.launcher,this);launcherImg=(ImageView)findViewById(R.id.launcher_img);width=launcherImg.getLayoutParams().width;height=launcherImg.getLayoutParams().height;}/***更新火箭发射台的显示状态。如果小火箭被拖到火箭发射台上,就显示发射。*/publicvoidupdateLauncherStatus(booleanisReadyToLaunch){if(isReadyToLaunch){launcherImg.setImageResource(R.drawable.launcher_bg_fire);}else{launcherImg.setImageResource(R.drawable.launcher_bg_hold);}}}

RocketLauncher中的代码还是非常简单的,在构建方法中调用了LayoutInflater的inflate()方法来将launcher.xml这个布局文件加载进来,并获取到了当前View的宽度和高度。在updateLauncherStatus()方法中会进行判断,如果传入的参数是true,就显示小火箭即将发射的图片,如果传入的是false,就显示将小火箭拖动到发射台的图片。

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

publicclassMyWindowManager{/***小悬浮窗View的实例*/privatestaticFloatWindowSmallViewsmallWindow;/***大悬浮窗View的实例*/privatestaticFloatWindowBigViewbigWindow;/***火箭发射台的实例*/privatestaticRocketLauncherrocketLauncher;/***小悬浮窗View的参数*/privatestaticLayoutParamssmallWindowParams;/***大悬浮窗View的参数*/privatestaticLayoutParamsbigWindowParams;/***火箭发射台的参数*/privatestaticLayoutParamslauncherParams;/***用于控制在屏幕上添加或移除悬浮窗*/privatestaticWindowManagermWindowManager;/***用于获取手机可用内存*/privatestaticActivityManagermActivityManager;/***创建一个小悬浮窗。初始位置为屏幕的右部中间位置。*/publicstaticvoidcreateSmallWindow(Contextcontext){WindowManagerwindowManager=getWindowManager(context);intscreenWidth=windowManager.getDefaultDisplay().getWidth();intscreenHeight=windowManager.getDefaultDisplay().getHeight();if(smallWindow==null){smallWindow=newFloatWindowSmallView(context);if(smallWindowParams==null){smallWindowParams=newLayoutParams();smallWindowParams.type=LayoutParams.TYPE_SYSTEM_ALERT;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.windowViewWidth;smallWindowParams.height=FloatWindowSmallView.windowViewHeight;smallWindowParams.x=screenWidth;smallWindowParams.y=screenHeight/2;}smallWindow.setParams(smallWindowParams);windowManager.addView(smallWindow,smallWindowParams);}}/***将小悬浮窗从屏幕上移除。*/publicstaticvoidremoveSmallWindow(Contextcontext){if(smallWindow!=null){WindowManagerwindowManager=getWindowManager(context);windowManager.removeView(smallWindow);smallWindow=null;}}/***创建一个大悬浮窗。位置为屏幕正中间。*/publicstaticvoidcreateBigWindow(Contextcontext){WindowManagerwindowManager=getWindowManager(context);intscreenWidth=windowManager.getDefaultDisplay().getWidth();intscreenHeight=windowManager.getDefaultDisplay().getHeight();if(bigWindow==null){bigWindow=newFloatWindowBigView(context);if(bigWindowParams==null){bigWindowParams=newLayoutParams();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);}}/***将大悬浮窗从屏幕上移除。*/publicstaticvoidremoveBigWindow(Contextcontext){if(bigWindow!=null){WindowManagerwindowManager=getWindowManager(context);windowManager.removeView(bigWindow);bigWindow=null;}}/***创建一个火箭发射台,位置为屏幕底部。*/publicstaticvoidcreateLauncher(Contextcontext){WindowManagerwindowManager=getWindowManager(context);intscreenWidth=windowManager.getDefaultDisplay().getWidth();intscreenHeight=windowManager.getDefaultDisplay().getHeight();if(rocketLauncher==null){rocketLauncher=newRocketLauncher(context);if(launcherParams==null){launcherParams=newLayoutParams();launcherParams.x=screenWidth/2-RocketLauncher.width/2;launcherParams.y=screenHeight-RocketLauncher.height;launcherParams.type=LayoutParams.TYPE_PHONE;launcherParams.format=PixelFormat.RGBA_8888;launcherParams.gravity=Gravity.LEFT|Gravity.TOP;launcherParams.width=RocketLauncher.width;launcherParams.height=RocketLauncher.height;}windowManager.addView(rocketLauncher,launcherParams);}}/***将火箭发射台从屏幕上移除。*/publicstaticvoidremoveLauncher(Contextcontext){if(rocketLauncher!=null){WindowManagerwindowManager=getWindowManager(context);windowManager.removeView(rocketLauncher);rocketLauncher=null;}}/***更新火箭发射台的显示状态。*/publicstaticvoidupdateLauncher(){if(rocketLauncher!=null){rocketLauncher.updateLauncherStatus(isReadyToLaunch());}}/***更新小悬浮窗的TextView上的数据,显示内存使用的百分比。**@paramcontext*可传入应用程序上下文。*/publicstaticvoidupdateUsedPercent(Contextcontext){if(smallWindow!=null){TextViewpercentView=(TextView)smallWindow.findViewById(R.id.percent);percentView.setText(getUsedPercentValue(context));}}/***是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。**@return有悬浮窗显示在桌面上返回true,没有的话返回false。*/publicstaticbooleanisWindowShowing(){returnsmallWindow!=null||bigWindow!=null;}/***判断小火箭是否准备好发射了。**@return当火箭被发到发射台上返回true,否则返回false。*/publicstaticbooleanisReadyToLaunch(){if((smallWindowParams.x>launcherParams.x&&smallWindowParams.x+smallWindowParams.width<launcherParams.x+launcherParams.width)&&(smallWindowParams.y+smallWindowParams.height>launcherParams.y)){returntrue;}returnfalse;}/***如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。**@paramcontext*必须为应用程序的Context.*@returnWindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。*/privatestaticWindowManagergetWindowManager(Contextcontext){if(mWindowManager==null){mWindowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);}returnmWindowManager;}/***如果ActivityManager还未创建,则创建一个新的ActivityManager返回。否则返回当前已创建的ActivityManager。**@paramcontext*可传入应用程序上下文。*@returnActivityManager的实例,用于获取手机可用内存。*/privatestaticActivityManagergetActivityManager(Contextcontext){if(mActivityManager==null){mActivityManager=(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);}returnmActivityManager;}/***计算已使用内存的百分比,并返回。**@paramcontext*可传入应用程序上下文。*@return已使用内存的百分比,以字符串形式返回。*/publicstaticStringgetUsedPercentValue(Contextcontext){Stringdir="/proc/meminfo";try{FileReaderfr=newFileReader(dir);BufferedReaderbr=newBufferedReader(fr,2048);StringmemoryLine=br.readLine();StringsubMemoryLine=memoryLine.substring(memoryLine.indexOf("MemTotal:"));br.close();longtotalMemorySize=Integer.parseInt(subMemoryLine.replaceAll("\\D+",""));longavailableSize=getAvailableMemory(context)/1024;intpercent=(int)((totalMemorySize-availableSize)/(float)totalMemorySize*100);returnpercent+"%";}catch(IOExceptione){e.printStackTrace();}return"悬浮窗";}/***获取当前可用内存,返回数据以字节为单位。**@paramcontext*可传入应用程序上下文。*@return当前可用内存。*/privatestaticlonggetAvailableMemory(Contextcontext){ActivityManager.MemoryInfomi=newActivityManager.MemoryInfo();getActivityManager(context).getMemoryInfo(mi);returnmi.availMem;}}

MyWindowManager是所有桌面悬浮窗的管理器,这里我们主要添加了createLauncher()、removeLauncher()和updateLauncher()这几个方法,分别用于创建、移除、以及更新火箭发射台悬浮窗。另外还添加了isReadyToLaunch()这个方法,它是用于判断小火箭是否已经拖动到火箭发射台上了。判断的方式当然也很简单,只需要对小火箭的边界和火箭发射台的边界进行检测,判断它们是否相交就行了。

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

publicclassFloatWindowSmallViewextendsLinearLayout{/***记录小悬浮窗的宽度*/publicstaticintwindowViewWidth;/***记录小悬浮窗的高度*/publicstaticintwindowViewHeight;/***记录系统状态栏的高度*/privatestaticintstatusBarHeight;/***用于更新小悬浮窗的位置*/privateWindowManagerwindowManager;/***小悬浮窗的布局*/privateLinearLayoutsmallWindowLayout;/***小火箭控件*/privateImageViewrocketImg;/***小悬浮窗的参数*/privateWindowManager.LayoutParamsmParams;/***记录当前手指位置在屏幕上的横坐标值*/privatefloatxInScreen;/***记录当前手指位置在屏幕上的纵坐标值*/privatefloatyInScreen;/***记录手指按下时在屏幕上的横坐标的值*/privatefloatxDownInScreen;/***记录手指按下时在屏幕上的纵坐标的值*/privatefloatyDownInScreen;/***记录手指按下时在小悬浮窗的View上的横坐标的值*/privatefloatxInView;/***记录手指按下时在小悬浮窗的View上的纵坐标的值*/privatefloatyInView;/***记录小火箭的宽度*/privateintrocketWidth;/***记录小火箭的高度*/privateintrocketHeight;/***记录当前手指是否按下*/privatebooleanisPressed;publicFloatWindowSmallView(Contextcontext){super(context);windowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);LayoutInflater.from(context).inflate(R.layout.float_window_small,this);smallWindowLayout=(LinearLayout)findViewById(R.id.small_window_layout);windowViewWidth=smallWindowLayout.getLayoutParams().width;windowViewHeight=smallWindowLayout.getLayoutParams().height;rocketImg=(ImageView)findViewById(R.id.rocket_img);rocketWidth=rocketImg.getLayoutParams().width;rocketHeight=rocketImg.getLayoutParams().height;TextViewpercentView=(TextView)findViewById(R.id.percent);percentView.setText(MyWindowManager.getUsedPercentValue(context));}@OverridepublicbooleanonTouchEvent(MotionEventevent){switch(event.getAction()){caseMotionEvent.ACTION_DOWN:isPressed=true;//手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度xInView=event.getX();yInView=event.getY();xDownInScreen=event.getRawX();yDownInScreen=event.getRawY()-getStatusBarHeight();xInScreen=event.getRawX();yInScreen=event.getRawY()-getStatusBarHeight();break;caseMotionEvent.ACTION_MOVE:xInScreen=event.getRawX();yInScreen=event.getRawY()-getStatusBarHeight();//手指移动的时候更新小悬浮窗的状态和位置updateViewStatus();updateViewPosition();break;caseMotionEvent.ACTION_UP:isPressed=false;if(MyWindowManager.isReadyToLaunch()){launchRocket();}else{updateViewStatus();//如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。if(xDownInScreen==xInScreen&&yDownInScreen==yInScreen){openBigWindow();}}break;default:break;}returntrue;}/***将小悬浮窗的参数传入,用于更新小悬浮窗的位置。**@paramparams*小悬浮窗的参数*/publicvoidsetParams(WindowManager.LayoutParamsparams){mParams=params;}/***用于发射小火箭。*/privatevoidlaunchRocket(){MyWindowManager.removeLauncher(getContext());newLaunchTask().execute();}/***更新小悬浮窗在屏幕中的位置。*/privatevoidupdateViewPosition(){mParams.x=(int)(xInScreen-xInView);mParams.y=(int)(yInScreen-yInView);windowManager.updateViewLayout(this,mParams);MyWindowManager.updateLauncher();}/***更新View的显示状态,判断是显示悬浮窗还是小火箭。*/privatevoidupdateViewStatus(){if(isPressed&&rocketImg.getVisibility()!=View.VISIBLE){mParams.width=rocketWidth;mParams.height=rocketHeight;windowManager.updateViewLayout(this,mParams);smallWindowLayout.setVisibility(View.GONE);rocketImg.setVisibility(View.VISIBLE);MyWindowManager.createLauncher(getContext());}elseif(!isPressed){mParams.width=windowViewWidth;mParams.height=windowViewHeight;windowManager.updateViewLayout(this,mParams);smallWindowLayout.setVisibility(View.VISIBLE);rocketImg.setVisibility(View.GONE);MyWindowManager.removeLauncher(getContext());}}/***打开大悬浮窗,同时关闭小悬浮窗。*/privatevoidopenBigWindow(){MyWindowManager.createBigWindow(getContext());MyWindowManager.removeSmallWindow(getContext());}/***用于获取状态栏的高度。**@return返回状态栏高度的像素值。*/privateintgetStatusBarHeight(){if(statusBarHeight==0){try{Class<?>c=Class.forName("com.android.internal.R$dimen");Objecto=c.newInstance();Fieldfield=c.getField("status_bar_height");intx=(Integer)field.get(o);statusBarHeight=getResources().getDimensionPixelSize(x);}catch(Exceptione){e.printStackTrace();}}returnstatusBarHeight;}/***开始执行发射小火箭的任务。**@authorguolin*/classLaunchTaskextendsAsyncTask<Void,Void,Void>{@OverrideprotectedVoiddoInBackground(Void...params){//在这里对小火箭的位置进行改变,从而产生火箭升空的效果while(mParams.y>0){mParams.y=mParams.y-10;publishProgress();try{Thread.sleep(8);}catch(InterruptedExceptione){e.printStackTrace();}}returnnull;}@OverrideprotectedvoidonProgressUpdate(Void...values){windowManager.updateViewLayout(FloatWindowSmallView.this,mParams);}@OverrideprotectedvoidonPostExecute(Voidresult){//火箭升空结束后,回归到悬浮窗状态updateViewStatus();mParams.x=(int)(xDownInScreen-xInView);mParams.y=(int)(yDownInScreen-yInView);windowManager.updateViewLayout(FloatWindowSmallView.this,mParams);}}}

这里在代码中添加了一个isPressed标识位,用于判断用户是否正在拖动悬浮窗。当拖动的时候就调用updateViewStatus()方法来更新悬浮窗的显示状态,这时悬浮窗就会变成一个小火箭。然后当手指离开屏幕的时候,也会调用updateViewStatus()方法,这时发现isPressed为false,就会将悬浮窗重新显示出来。

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

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

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

代码就只有这么多,接下来我们运行一下看看效果吧。在主界面点击Start Float Window按钮可以开启悬浮窗并回到桌面,然后拖动悬浮窗后就会变成小火箭的状态,将它拖动到屏幕底部火箭发射台上,然后放手,小火箭就会腾空而起了,如下图所示:

好了,今天的讲解就到这里,伴随着小火箭的起飞。

源码下载,请点击这里

更多相关文章

  1. Android仿微信文章悬浮窗效果的实现代码
  2. Android(安卓)为你的应用添加悬浮窗功能
  3. Android6.0(Android(安卓)M) 悬浮窗被禁用,无权限开启悬浮窗的解决
  4. Android悬浮贴边按钮实现(含动画效果)
  5. Presentation 双屏异显
  6. Android滑动到某个界面悬浮置顶的解决
  7. 始终悬浮在Android屏幕的弹窗
  8. Android(安卓)Design Support Library - FloatingActionButton
  9. Android悬浮窗口基本知识

随机推荐

  1. fix Android(安卓)building error on ubu
  2. Android平台上的高性能编程
  3. Android网络编程——https 不验证证书方
  4. [译文]移动应用开发,第1部分:在Android上应
  5. Android无法自动创建以usb开头的节点
  6. Android 3 使用 smartTable 表格工具 实
  7. [置顶] Android 的媒体路由功能应用与框
  8. Android 滑动切换页面 以及屏幕手势
  9. 在线自动生成.9png图的Android设计切图工
  10. Android花样loading进度条(四)-渐变色环形