MainActivity如下:
package cc.cc; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.app.Activity; import android.content.Context; import android.content.Intent; /** * Demo描述: * 仿360在Launcher画面显示内存使用率的浮窗. * 当然这里只是简单地仿O(∩_∩)O * * 思路整理: * 1 涉及到大小两个浮窗.并且两个浮窗之间有逻辑联系.比如: * 显示小浮窗时不显示大浮窗.所以利用DriftingWindowManager * 来管理这两个浮窗 * 2 各用一个类来封装和实现两个浮窗的操作 * 3 以上两个类均继承自Layout * * 总的来讲该示例还是比较简单的 * * 学习资料: * 1 http://blog.csdn.net/guolin_blog/article/details/8689140 * 2 http://blog.csdn.net/feng88724/article/details/6362710 * 3 http://blog.csdn.net/hudashi/article/details/6901118 * 4 http://blog.csdn.net/hudashi/article/details/7060882 * 5 http://blog.csdn.net/hudashi/article/details/7061240 * Thank you very much * */ public class MainActivity extends Activity { private Context mContext; private Button mStartButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); init(); } private void init(){ mContext=this; mStartButton=(Button) findViewById(R.id.button); mStartButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(); intent.setAction("dws"); mContext.startService(intent); finish(); } }); } }
DriftingBigWindow如下:
package cc.cc; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; /** *大浮窗 * *大浮窗继承自LinearLayout *该大浮窗主要实现的功能 *1 关闭大浮窗显示小浮窗 *2 关闭所有浮窗且停止对于内存使用率的监控 * *注意方法: *LayoutInflater.inflate(int resource, ViewGroup root)的第二参数 *若设置了root,那么会把新生成的View连接到root,该方法的返回为root. *否未设置root,则返回的是新生成的View */ public class DriftingBigWindow extends LinearLayout { //整个大浮窗的宽和高 public static int width=0; public static int height=0; private Context mContext; public DriftingBigWindow(Context context) { super(context); mContext=context; LayoutInflater layoutInflater=LayoutInflater.from(mContext); View bigWindowView=layoutInflater.inflate(R.layout.drifting_window_big, this); View driftingBigWindowRootView=bigWindowView.findViewById(R.id.driftingBigWindowRootView); //获取大浮窗整个布局的宽和高 width=driftingBigWindowRootView.getLayoutParams().width; height=driftingBigWindowRootView.getLayoutParams().height; //停止服务且移除浮窗 Button closeButton=(Button) bigWindowView.findViewById(R.id.closeButton); closeButton.setOnClickListener(new ClickListenerImpl()); //显示小浮窗 Button backButton=(Button) bigWindowView.findViewById(R.id.backButton); backButton.setOnClickListener(new ClickListenerImpl()); } private class ClickListenerImpl implements OnClickListener{ @Override public void onClick(View view) { switch (view.getId()) { case R.id.closeButton: Intent intent=new Intent(); intent.setAction("dws"); mContext.stopService(intent); DriftingWindowManager.removeDriftingSmallWindow(mContext); DriftingWindowManager.removeDriftingBiglWindow(mContext); break; case R.id.backButton: DriftingWindowManager.showDriftingSmallWindow(mContext); DriftingWindowManager.removeDriftingBiglWindow(mContext); break; default: break; } } } }
DriftingSmallWindow如下:
package cc.cc; import android.content.Context; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; /** *小浮窗 * *小浮窗继承自LinearLayout *该小浮窗主要实现的功能: *1 显示手机的内存使用率 *2 在Home界面被随意移动位置 * 所以重点是实现TouchListener,在Touch监听中 * 不断调用mWindowManager.updateViewLayout() * 修改小浮窗在屏幕上的LayoutParams * *注意方法: *LayoutInflater.inflate(int resource, ViewGroup root)的第二参数 *若设置了root,那么会把新生成的View连接到root,该方法的返回为root. *否未设置root,则返回的是新生成的View */ public class DriftingSmallWindow extends LinearLayout { //整个小浮窗的宽和高 public static int width=0; public static int height=0; private float XInScreen_Down = 0; private float YInScreen_Down = 0; private float XInScreen_Move = 0; private float YInScreen_Move = 0; private float XInScreen_Up = 0; private float YInScreen_Up = 0; private float XInView_Down=0; private float YInView_Down=0; private Context mContext; private WindowManager mWindowManager; private static WindowManager.LayoutParams mWindowManagerLayoutParams; public DriftingSmallWindow(Context context) { super(context); mContext=context; mWindowManager=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE); LayoutInflater layoutInflater = LayoutInflater.from(context); View smallWindowView=layoutInflater.inflate(R.layout.drifting_window_small, this); View driftingSmallWindowRootView=smallWindowView.findViewById(R.id.driftingSmallWindowRootView); TextView percentTextView=(TextView) smallWindowView.findViewById(R.id.percentTextView); //获取小浮窗整个布局的宽和高 width=driftingSmallWindowRootView.getLayoutParams().width; height=driftingSmallWindowRootView.getLayoutParams().height; percentTextView.setText(Utils.getAvailMemoryPercent(context)); this.setOnTouchListener(new TouchListenerImpl()); //this.setOnClickListener(new ClickListenerImpl()); } /** * 利用该方式监听点击事件不靠谱 * 所以在MotionEvent.ACTION_UP中处理点击事件 */ private class ClickListenerImpl implements OnClickListener{ @Override public void onClick(View view) { } } /** * 对于小浮窗Touch事件的监听 * 注意在MotionEvent.ACTION_UP中处理点击事件 */ private class TouchListenerImpl implements OnTouchListener{ @Override public boolean onTouch(View view, MotionEvent event) { int statusBarHeight=Utils.getStatusBarHeight(mContext); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: XInScreen_Down=event.getRawX(); YInScreen_Down=event.getRawY()-statusBarHeight; XInView_Down=event.getX(); YInView_Down=event.getY(); break; case MotionEvent.ACTION_MOVE: XInScreen_Move=event.getRawX(); YInScreen_Move=event.getRawY()-statusBarHeight; updateWindowManagerLayoutParams(); break; case MotionEvent.ACTION_UP: XInScreen_Up=event.getRawX(); YInScreen_Up=event.getRawY()-statusBarHeight; if (XInScreen_Down==XInScreen_Up&&YInScreen_Down==YInScreen_Up) { showDriftingBigWindow(); } break; default: break; } return true; } }; /** * 保存小浮窗在Window中的布局参数 */ public static void saveWindowManagerLayoutParams(WindowManager.LayoutParams layoutParams){ mWindowManagerLayoutParams=layoutParams; } /** * 更新小浮窗在Window中的布局参数 * * 注意事项: * X(Y)InScreen_Move表示触摸点离屏幕左上角的距离 * X(Y)InView_Down表示触摸点离DriftingSmallWindow(小浮窗)自身左上角的距离. * 两者相减即得DriftingSmallWindow(小浮窗)左上角的坐标 */ private void updateWindowManagerLayoutParams(){ mWindowManagerLayoutParams.x=(int) (XInScreen_Move-XInView_Down); mWindowManagerLayoutParams.y=(int) (YInScreen_Move-YInView_Down); mWindowManager.updateViewLayout(this, mWindowManagerLayoutParams); } /** * 显示大浮窗且关闭小浮窗 */ private void showDriftingBigWindow(){ DriftingWindowManager.showDriftingBiglWindow(mContext); DriftingWindowManager.removeDriftingSmallWindow(mContext); } }
DriftingWindowManager如下:
package cc.cc; import android.content.Context; import android.graphics.PixelFormat; import android.view.Gravity; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.TextView; /** * 管理大小浮动窗口 * 在该应用中主要包含了大小两个浮动窗口及其对应的操作 * 比如显示,更新和移除等,所以就写了个DriftingWindowManager * 来实施这些操作. * 至于大小浮窗各自要做的操作则是在DriftingSmallWindow和DriftingBigWindow * 各个类中具体实施的. * 这个和平时其他的代码原理是一样的: * 比如在一个类A中使用了(类似于此处的浮窗显示,更新,移除)B和C的对象. * 但B和C对象的方法是在各自的类中实现的. */ public class DriftingWindowManager { private static WindowManager mWindowManager=null; private static DriftingSmallWindow mDriftingSmallWindow=null; private static DriftingBigWindow mDriftingBigWindow=null; //注意该LayoutParams属于android.view.WindowManager.LayoutParams private static LayoutParams mDriftingSmallWindowLayoutParams; private static LayoutParams mDriftingBigWindowLayoutParams; /** * 显示小浮窗 * 显示位置为屏幕中间右对齐 */ public static void showDriftingSmallWindow(Context context) { WindowManager windowManager = getWindowManager(context); int screenWidth = windowManager.getDefaultDisplay().getWidth(); int screenHeight = windowManager.getDefaultDisplay().getHeight(); //new了一个DriftingSmallWindow对象,在后面会用WindowManager将 //其添加到屏幕中 mDriftingSmallWindow = new DriftingSmallWindow(context); if (mDriftingSmallWindowLayoutParams == null) { mDriftingSmallWindowLayoutParams = new LayoutParams(); mDriftingSmallWindowLayoutParams.type = LayoutParams.TYPE_PHONE; mDriftingSmallWindowLayoutParams.format = PixelFormat.RGBA_8888; mDriftingSmallWindowLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE; mDriftingSmallWindowLayoutParams.gravity = Gravity.LEFT| Gravity.TOP; mDriftingSmallWindowLayoutParams.width = DriftingSmallWindow.width; mDriftingSmallWindowLayoutParams.height = DriftingSmallWindow.height; //使小浮窗在屏幕上垂直居中,水平靠右的位置显示 mDriftingSmallWindowLayoutParams.x = screenWidth-DriftingSmallWindow.width; mDriftingSmallWindowLayoutParams.y = screenHeight / 2; System.out.println("DriftingSmallWindow.width="+DriftingSmallWindow.width); System.out.println("mDriftingSmallWindowLayoutParams.x="+mDriftingSmallWindowLayoutParams.x); System.out.println("mDriftingSmallWindowLayoutParams.y="+mDriftingSmallWindowLayoutParams.y); } //当显示小浮窗的时保存小浮窗的LayoutParams至该DriftingSmallWindow对象 //因为每次移动小浮窗的时候需要修改该LayoutParams的参数值X和Y mDriftingSmallWindow.saveWindowManagerLayoutParams(mDriftingSmallWindowLayoutParams); mWindowManager.addView(mDriftingSmallWindow,mDriftingSmallWindowLayoutParams); } /** * 更新小浮窗 */ public static void updateDriftingSmallWindow(Context context){ if(mDriftingSmallWindow!=null){ TextView percentTextView=(TextView) mDriftingSmallWindow.findViewById(R.id.percentTextView); percentTextView.setText(Utils.getAvailMemoryPercent(context)); } } /** * 移除小浮窗 */ public static void removeDriftingSmallWindow(Context context){ mWindowManager=getWindowManager(context); if(mWindowManager!=null&&mDriftingSmallWindow!=null){ mWindowManager.removeView(mDriftingSmallWindow); mDriftingSmallWindow=null; } } /** * 显示大浮窗 * 显示位置为屏幕中间 * * 注意细节问题 * 如下写法,有偏差,显示效果并不好 * mDriftingBigWindowLayoutParams.x = screenWidth / 2; * mDriftingBigWindowLayoutParams.y = screenHeight / 2; * 给人的感觉是大浮窗并没有在屏幕中间位置. * 因为这个mDriftingBigWindowLayoutParams.x(y)指的是大浮窗 * 在屏幕上显示的x(y)的开始坐标值,即从哪个坐标开始摆放大浮窗. * 极端地说如果大浮窗就沙子那么大,那么这么做就没有问题,因为大浮窗 * 本身就没有什么宽和高. * 但在实际中我们还要考虑到控件本身(此处的大浮窗)的长和宽,做到 * 真的居中显示 * 所以应该这么写: * mDriftingBigWindowLayoutParams.x = screenWidth / 2- DriftingBigWindow.width / 2; * mDriftingBigWindowLayoutParams.y = screenHeight / 2- DriftingBigWindow.height / 2; * 类似的问题在小浮窗的拖动过程中也有 */ public static void showDriftingBiglWindow(Context context) { WindowManager windowManager = getWindowManager(context); int screenWidth = windowManager.getDefaultDisplay().getWidth(); int screenHeight = windowManager.getDefaultDisplay().getHeight(); mDriftingBigWindow = new DriftingBigWindow(context); if (mDriftingBigWindowLayoutParams == null) { mDriftingBigWindowLayoutParams = new LayoutParams(); mDriftingBigWindowLayoutParams.x = screenWidth / 2- DriftingBigWindow.width / 2; mDriftingBigWindowLayoutParams.y = screenHeight / 2- DriftingBigWindow.height / 2; mDriftingBigWindowLayoutParams.type = LayoutParams.TYPE_PHONE; mDriftingBigWindowLayoutParams.format = PixelFormat.RGBA_8888; mDriftingBigWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; mDriftingBigWindowLayoutParams.width = DriftingBigWindow.width; mDriftingBigWindowLayoutParams.height = DriftingBigWindow.height; } windowManager.addView(mDriftingBigWindow,mDriftingBigWindowLayoutParams); } /** * 移除大浮窗 */ public static void removeDriftingBiglWindow(Context context){ mWindowManager=getWindowManager(context); if(mWindowManager!=null&&mDriftingBigWindow!=null){ mWindowManager.removeView(mDriftingBigWindow); mDriftingBigWindow=null; } } /** * 是否有浮窗在Launcher上显示 */ public static boolean isDriftingWindowShowing(){ if (mDriftingSmallWindow!=null||mDriftingBigWindow!=null) { return true; } else { return false; } } /** * 获取WindowManager */ private static WindowManager getWindowManager(Context context){ if (mWindowManager==null) { mWindowManager=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } return mWindowManager; } }
DriftingWindowService如下:
package cc.cc; import java.util.Timer; import java.util.TimerTask; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; /** * 利用该服务进行定时任务 * */ public class DriftingWindowService extends Service { private Timer mTimer; private TimerTask mTimerTask; private Context mContext; private Handler mHandler; @Override public void onCreate() { super.onCreate(); } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); mContext=this; mHandler=new Handler(); mTimer=new Timer(); mTimerTask=new TimerTaskSubclass(); //开启定时的任务 mTimer.schedule(mTimerTask, 100, 500); } @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onDestroy() { super.onDestroy(); if (mTimer!=null) { mTimer.cancel(); } } private class TimerTaskSubclass extends TimerTask{ @Override public void run() { //当前是Launcher,则显示小浮窗 if (Utils.currentIsLauncher(mContext)&&!DriftingWindowManager.isDriftingWindowShowing()) { mHandler.post(new Runnable() { @Override public void run() { DriftingWindowManager.showDriftingSmallWindow(mContext); } }); } //当前不是Launcher且有浮窗显示,则移除浮窗 if(!Utils.currentIsLauncher(mContext)&&DriftingWindowManager.isDriftingWindowShowing()){ mHandler.post(new Runnable() { @Override public void run() { DriftingWindowManager.removeDriftingSmallWindow(mContext); DriftingWindowManager.removeDriftingBiglWindow(mContext); } }); } //当前是Launcher,则更新内存使用率 if(Utils.currentIsLauncher(mContext)){ mHandler.post(new Runnable() { @Override public void run() { DriftingWindowManager.updateDriftingSmallWindow(mContext); } }); } } } }
Utils如下:
package cc.cc; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; import java.util.List; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; import android.app.ActivityManager.RunningTaskInfo; import android.content.ComponentName; import android.content.Context; public class Utils { /** * 获取设备状态栏高度 */ public static int getStatusBarHeight(Context context) { int statusBarHeight = 0; try { Class clazz = Class.forName("com.android.internal.R$dimen"); Object object = clazz.newInstance(); Field field = clazz.getField("status_bar_height"); // 反射出该对象中status_bar_height字段所对应的在R文件的id值 // 该id值由系统工具自动生成,文档描述如下: // The desired resource identifier, as generated by the aapt tool. int id = Integer.parseInt(field.get(object).toString()); // 依据id值获取到状态栏的高度,单位为像素 statusBarHeight = context.getResources().getDimensionPixelSize(id); } catch (Exception e) { } return statusBarHeight; } /** * 判断设备当前是否停留在Launcher */ public static boolean currentIsLauncher(Context context){ boolean isLauncher=false; String topActivityName=getTopActivityName(context); if (topActivityName!=null&&topActivityName.startsWith("HomeActivity")) { isLauncher=true; } return isLauncher; } /** * 获取栈顶Activity名称 */ public static String getTopActivityName(Context context) { String topActivityName = null; ActivityManager activityManager = (ActivityManager)(context.getSystemService(android.content.Context.ACTIVITY_SERVICE)); List<RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(1); if (runningTaskInfos != null) { ComponentName f = runningTaskInfos.get(0).topActivity; String topActivityClassName = f.getClassName(); String temp[] = topActivityClassName.split("\\."); // 栈顶Activity的名称 topActivityName = temp[temp.length - 1]; } return topActivityName; } /** * 获取当前内存的可用率 */ public static String getAvailMemoryPercent(Context context){ String info=null; long availMemory=getAvailMemory(context); long totalMemory=getTotalMemory(); float percent=(availMemory*100/totalMemory); info=percent+"%"; return info; } /** * 获取内存总大小 */ public static long getTotalMemory() { // 系统的内存信息文件 String filePath = "/proc/meminfo"; String lineString; String[] stringArray; long totalMemory = 0; try { FileReader fileReader = new FileReader(filePath); BufferedReader bufferedReader = new BufferedReader(fileReader,1024 * 8); // 读取meminfo第一行,获取系统总内存大小 lineString = bufferedReader.readLine(); // 按照空格拆分 stringArray = lineString.split("\\s+"); // 获得系统总内存,单位KB totalMemory = Integer.valueOf(stringArray[1]).intValue(); bufferedReader.close(); } catch (IOException e) { } return totalMemory / 1024; } /** * 获取可用内存大小 */ public static long getAvailMemory(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); MemoryInfo memoryInfo = new MemoryInfo(); activityManager.getMemoryInfo(memoryInfo); return memoryInfo.availMem / (1024 * 1024); } }
main.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开启浮动窗口" android:layout_centerInParent="true" /> </RelativeLayout>
drifting_window_big.xml如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/driftingBigWindowRootView" android:layout_width="130dip" android:layout_height="130dip" android:orientation="vertical" > <Button android:id="@+id/closeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="关闭所有浮窗" /> <Button android:id="@+id/backButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="返回到小浮窗" /> </LinearLayout>
drifting_window_small.xml如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/driftingSmallWindowRootView" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="45dip" android:layout_height="30dip" android:background="#00cc00" android:orientation="horizontal" > <TextView android:id="@+id/percentTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textColor="#ff0000" android:layout_gravity="center_vertical" /> </LinearLayout>
AndroidManifest.xml如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc.cc" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="10" /> <!-- 注意权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.GET_TASKS"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="cc.cc.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="cc.cc.DriftingWindowService"> <intent-filter > <action android:name="dws"/> </intent-filter> </service> </application> </manifest>