仿360安全卫士悬浮窗
虽然360安全卫士很流氓,但是我相信安装的人不在少数,它有一个让人很忧伤的功能,就是即时你关闭了360安全卫士,你手机的左边距或者右边距会有一个虫虫一样的东西,美其名曰:帮助用户清理内存的,今天我们就来讲一下360悬浮窗怎么玩,还是先贴图:
虽然丑了点,但主要在功能,想做漂亮,你们自己找美工P图吧!
说道悬浮窗,大家可能想到很多方法可以实现,但是怎样才能让它在退出Activity之后依然存在呢,首先还是讲思路,我们需要解决三大问题:
一、我们必须要想到一个载体,类似状态栏中的通知,是不是想到了,这个载体就是service,service的生命周期保证了悬浮窗不会随着Activity退出而被干掉。
二、我们必须要保证悬浮窗能够显示在launch上,这里我给大家推荐一个好东西,WindowManager,他能帮助你很好的解决这个问题。
三、如何获取内存使用信息,这一点我已经在上一章讲过,就不多说了,自己去看:内存获取
问题解决了,我们就开始贴代码部分吧(直接复制可用):
至于如何调用,那就再简单不过了:public class FwService extends Service { private static ActivityManager mActivityManager; //定义浮动窗口布局 LinearLayout mFloatLayout; WindowManager.LayoutParams wmParams; //创建浮动窗口设置布局参数的对象 static WindowManager mWindowManager; Button mFloatView; private static final String TAG = "FwService"; @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Log.i(TAG, "oncreat"); createFloatView(); //Toast.makeText(FxService.this, "create FxService", Toast.LENGTH_LONG); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } private int width, height; private void createFloatView() { wmParams = new LayoutParams(); mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE); //获取屏幕的高度 DisplayMetrics dm = new DisplayMetrics(); mWindowManager.getDefaultDisplay().getMetrics(dm); width = dm.widthPixels; height = dm.heightPixels; //设置window type wmParams.type = LayoutParams.TYPE_PHONE; //设置图片格式,效果为背景透明 wmParams.format = PixelFormat.RGBA_8888; //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL; //调整悬浮窗显示的停靠位置为右侧侧置顶,方便实现触摸滑动 wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 以屏幕左上角为原点,设置x、y初始值 wmParams.x = width; wmParams.y = height/2; //设置悬浮窗口长宽数据 wmParams.width = LinearLayout.LayoutParams.WRAP_CONTENT; wmParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; LayoutInflater inflater = LayoutInflater.from(getApplication()); //获取浮动窗口视图所在布局 mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null); //添加mFloatLayout mWindowManager.addView(mFloatLayout, wmParams); mFloatView = (Button) mFloatLayout.findViewById(R.id.float_id); // int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); // int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); // //设置layout大小 // mFloatLayout.measure(w, h); Log.i(TAG, "Width/2--->" + mFloatView.getMeasuredWidth() / 2); Log.i(TAG, "Height/2--->" + mFloatView.getMeasuredHeight() / 2); mFloatView.setText(getUsedPercentValue(getApplicationContext())); //设置监听浮动窗口的触摸移动 mFloatView.setOnTouchListener(new OnTouchListener() { float dx, dy, mx, my; float moveX, moveY; //这里用于up和move不冲突 boolean isMove; @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub //getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //记住手按下的位置 dx = event.getRawX(); dy = event.getRawY(); // mx = v.getWidth()/2; // my = v.getHeight()/2; //计算手相对控件本身按下的位置 mx = event.getX(); my = event.getY(); isMove = false;//这里需要设置默认值为false,避免up部分出bug return false; case MotionEvent.ACTION_MOVE: //计算手移动的距离 int x = Math.abs((int) (event.getRawX() - dx)); int y = Math.abs((int) (event.getRawY() - dy)); //如果x和y距离都小于5,说明用户并没打算移动,只是手触摸时产生的move if (x < 5 || y < 5) { isMove = false; return false; } else { isMove = true; } //计算控件移动的距离 x = (int) (event.getRawX() - mx); y = (int) (event.getRawY() - my); wmParams.x = x; //25为状态栏的高度 wmParams.y = y; //刷新 mWindowManager.updateViewLayout(mFloatLayout, wmParams); return true; case MotionEvent.ACTION_UP: //这里要注意边界问题 float finalX = event.getRawX(); float finalY = event.getRawY(); //控制上边距 if (finalY < v.getHeight()) { moveX = 0; moveY = finalX - my; } //下边距 if (finalY > height - v.getHeight()) { moveX = finalX - mx; moveY = finalY - v.getHeight(); } //判断控件改停留在左边距还是右边距 if (finalX - v.getWidth() / 2 < width / 2) { moveX = 0; moveY = finalY - my; } else if (finalX - v.getWidth() / 2 > width / 2) { moveX = width - v.getWidth(); moveY = finalY - my; } wmParams.x = (int) moveX; wmParams.y = (int) moveY; if (isMove) { mWindowManager.updateViewLayout(mFloatLayout, wmParams); } return isMove;//false为down,true为move default: break; } //这里return ture,说明本次事件已经被处理,不会传给父亲 return false; } }); mFloatView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Toast.makeText(FwService.this, "onClick", Toast.LENGTH_SHORT).show(); } }); } /** * 计算已使用内存的百分比,并返回。 * * @param context * 可传入应用程序上下文。 * @return 已使用内存的百分比,以字符串形式返回。 */ public static String getUsedPercentValue(Context context) { //内存信息文件(CPU信息文件:/proc/cpuinfo)这两个文件是linux系统用来存储内存和CPU信息的 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(); //获取总的内存,这里需要注意的是replaceAll支持正则表达式"\\D"代表所有的字母字符,只保留数字部分 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); long currentMemory = mi.availMem; return currentMemory; } /** * 如果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; } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if (mFloatLayout != null) { mWindowManager.removeView(mFloatLayout); } } }
讲到这里就基本讲完了,主要还是思路,下一章我将为大家讲述仿360悬浮窗的进阶篇,代码部分会更优化。我的博客只要是功能模块的,都会给源码,看完博客内容的当然是免费,如果想奉献点积分给我,也可以去我的CSDN下载栏去下载Intent intent = new Intent(MainActivity.this, FwService.class); startService(intent);
源码(包含进阶篇):仿360悬浮窗