安卓悬浮窗Demo
在桌面上创建一个小的悬浮窗。点击小悬浮窗后会弹出一个大的窗口。大窗口上有2个按键,分别为返回与关闭。点击大窗口外的部分能返回小窗口。
小窗口可以*拖动。小窗口上显示的是当前内存使用率。
Demo包含以下几个主要文件:
-------------------------------
FloatWindowService.java 主服务
FloatWindowManager.java 管理类
MainActivity.java
WindowBig.java 大窗口
WindowSmall.java 小窗口
-------------------------------
activity_main.xml
window_big.xml 大窗口布局
window_small.xml 小窗口布局
点击图标后,直接启动FloatWindowService,显示小窗口。因此可以不加载主页面activity_main.xml
/****************************************************************************************************************************************/
以下为window_big.xml
<?xml verl sion="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="200dip" android:layout_height="130dip" android:id="@+id/big_window_layout" android:background="@drawable/bg_blue" android:orientation="vertical" > <TextView android:id="@+id/base" android:layout_width="0dip" android:layout_height="0dip" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <TextView android:id="@+id/text1" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignRight="@+id/text2" android:textColor="#00FFFF" /> <Button android:id="@+id/btn_big_1" android:layout_width="100dip" android:layout_height="40dip" android:layout_below="@id/base" android:background="@android:color/transparent" android:layout_centerHorizontal="true" android:text="close" /> <TextView android:id="@+id/text2" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/text1" android:textColor="#00FFFF" /> <Button android:id="@+id/btn_big_2" android:layout_width="100dip" android:layout_height="40dip" android:background="@android:color/transparent" android:layout_alignLeft="@+id/btn_big_1" android:layout_alignTop="@+id/text2" android:text="back" /> </RelativeLayout>
大窗体上放2个按钮。分别实现返回与关闭程序功能。
大窗体的id是big_window_layout
/****************************************************************************************************************************************/以下为小窗口
<?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>放置一个TextView,用来显示内存使用百分率。
id是small_window_layout
/****************************************************************************************************************************************/
WindowSmall继承LinearLayout
package com.rust.floatwindowdemo; import java.lang.reflect.Field; 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; public class WindowSmall extends LinearLayout{ private WindowManager windowManager; private WindowManager.LayoutParams mParams; private static int barHeight; public static int viewWidth; public static int viewHeight; private float firstX; private float firstY; private float currentX; private float currentY; private float viewX; private float viewY; public WindowSmall(Context context) { super(context); windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); LayoutInflater.from(context).inflate(R.layout.window_small, this); View view = findViewById(R.id.small_window_layout); viewWidth = view.getLayoutParams().width; viewHeight = view.getLayoutParams().height; TextView memoryUsePercent = (TextView) findViewById(R.id.percent); memoryUsePercent.setText(FloatWindowManager.getUsePercent(context)); } public void setParams(WindowManager.LayoutParams params){ mParams = params; } @Override public boolean onTouchEvent(MotionEvent event){ switch(event.getAction()){ case MotionEvent.ACTION_DOWN: viewX = event.getX(); viewY = event.getY(); firstX = event.getRawX(); firstY = event.getRawY() - getBarHeight(); currentX = event.getRawX(); currentY = event.getRawY() - getBarHeight(); break; case MotionEvent.ACTION_MOVE: currentX = event.getRawX(); currentY = event.getRawY() - getBarHeight(); freshSmallWindow(); break; case MotionEvent.ACTION_UP: if(firstX == currentX && firstY == currentY){ startBigWindow(getContext()); } break; } return true; } /** * * @return 返回状态栏高度 */ private int getBarHeight(){ if(barHeight == 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); barHeight = getResources().getDimensionPixelSize(x); }catch(Exception e){ e.printStackTrace(); } } return barHeight; } private void freshSmallWindow(){ mParams.x = (int) (currentX - viewX); mParams.y = (int) (currentY - viewY); windowManager.updateViewLayout(this, mParams); } private void startBigWindow(Context context){ FloatWindowManager.removeSmallWindow(getContext()); FloatWindowManager.createBigWindow(getContext()); } }
/****************************************************************************************************************************************/
WindowBig继承RelativeLayout
package com.rust.floatwindowdemo; import android.content.Context; import android.content.Intent; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; public class WindowBig extends RelativeLayout{ private WindowManager windowManager; private WindowManager.LayoutParams mParams; private String strViewX; private String strfirstX; public static int bigViewWidth; public static int bigViewHeight; private static int screenWidth; private static int screenHeight; private float firstX; private float firstY; private float currentX; private float currentY; private float viewX; private float viewY; private int deltaX; private int deltaY; public WindowBig(final Context context) { super(context); windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); LayoutInflater.from(context).inflate(R.layout.window_big, this); screenWidth = windowManager.getDefaultDisplay().getWidth(); screenHeight = windowManager.getDefaultDisplay().getHeight(); View view = findViewById(R.id.big_window_layout); Button btn1 = (Button) findViewById(R.id.btn_big_1);//close all Button btn2 = (Button) findViewById(R.id.btn_big_2);//close big window bigViewWidth = view.getLayoutParams().width; bigViewHeight = view.getLayoutParams().height; deltaX = screenWidth/2 - bigViewWidth/2; btn1.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { FloatWindowManager.removeBigWindow(context); FloatWindowManager.removeSmallWindow(context); Toast.makeText(context, "bye bye", Toast.LENGTH_SHORT).show();; Intent intent = new Intent(getContext(),FloatWindowService.class); context.stopService(intent); } }); btn2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { FloatWindowManager.removeBigWindow(context); FloatWindowManager.createSmallWindow(context); } }); TextView tv1 = (TextView)findViewById(R.id.text1); } @Override public boolean onTouchEvent(MotionEvent event){ switch(event.getAction()){ case MotionEvent.ACTION_DOWN: viewX = event.getX(); viewY = event.getY(); firstX = event.getRawX(); firstY = event.getRawY(); currentX = event.getRawX(); currentY = event.getRawY(); // showXY(); break; case MotionEvent.ACTION_MOVE: currentX = event.getRawX(); currentY = event.getRawY(); break; case MotionEvent.ACTION_UP: if(viewX < 0 || viewX > bigViewWidth || viewY < 0 || viewY > bigViewHeight){ FloatWindowManager.removeBigWindow(getContext()); FloatWindowManager.createSmallWindow(getContext()); } break; } return true; } private void showXY(){ strViewX = Float.toString(bigViewHeight); TextView text1 = (TextView)findViewById(R.id.text1); text1.setText(strViewX); strfirstX = Float.toString(firstY); TextView text2 = (TextView)findViewById(R.id.text2); text2.setText(strfirstX); } }onTouchEvent中判断手指点击屏幕的位置。当点击到大窗体之外的部分,会显示小窗口。
getRawX()得到的是原点在屏幕最上角的X值。getX()得到的是以窗体左上角为原点的X值。
最开始是用计算的方式来判断是否点击到大窗体外。现在直接使用getRaw得到的值来和窗
体高度宽度相比较。
showXY()用来显示点击坐标。仅用于测试。
showXY()用来显示点击坐标。仅用于测试。
/****************************************************************************************************************************************/
MainActivity直接开启FloatWindowService
package com.rust.floatwindowdemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); Intent intent = new Intent(MainActivity.this , FloatWindowService.class); startService(intent); finish(); // Button startButton = (Button) findViewById(R.id.btn_1); // startButton.setOnClickListener(new OnClickListener(){ // // @Override // public void onClick(View v) { // Intent intent = new Intent(MainActivity.this , FloatWindowService.class); // startService(intent); // // finish(); // } // // }); }
/****************************************************************************************************************************************/
FloatWindowService
Service里开启一个0.5秒刷新一次的定时器。根据条件,决定悬浮窗的显示。
创建与删除窗体的方法在FloatWindowManager中。
package com.rust.floatwindowdemo; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Handler; import android.os.IBinder; 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 SettingTask(), 0, 500); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // Service被终止的同时也停止定时器继续运行 timer.cancel(); timer = null; } class SettingTask extends TimerTask { @Override public void run() { // 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。 if (OnTop() && !FloatWindowManager.isWindowShowing()) { handler.post(new Runnable() { @Override public void run() { FloatWindowManager.createSmallWindow(getApplicationContext()); } }); } // 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗。 else if (!OnTop() && FloatWindowManager.isWindowShowing()) { handler.post(new Runnable() { @Override public void run() { FloatWindowManager.removeSmallWindow(getApplicationContext()); FloatWindowManager.removeBigWindow(getApplicationContext()); } }); } // 当前界面是桌面,且有悬浮窗显示,则更新内存数据。 else if (OnTop() && FloatWindowManager.isWindowShowing()) { handler.post(new Runnable() { @Override public void run() { FloatWindowManager.updateUsedPercent(getApplicationContext()); } }); } } } /** * 判断当前界面是否是桌面 */ private boolean OnTop() { ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1); return getNames().contains(rti.get(0).topActivity.getPackageName()); } /** * 获得属于桌面的应用的应用包名称 * * @return 返回包含所有包名的字符串列表 */ private List<String> getNames() { 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; } }
/****************************************************************************************************************************************/
WindowManager,是用于控制的工具类。集合了常用功能。
package com.rust.floatwindowdemo; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import android.app.ActivityManager; 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; public class FloatWindowManager { /** * 小悬浮窗View的实例 */ private static WindowSmall smallWindow; /** * 大悬浮窗View的实例 */ private static WindowBig 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 WindowSmall(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 = WindowSmall.viewWidth; smallWindowParams.height = WindowSmall.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 WindowBig(context); if (bigWindowParams == null) { bigWindowParams = new LayoutParams(); bigWindowParams.x = screenWidth / 2 - WindowBig.bigViewWidth / 2; bigWindowParams.y = screenHeight / 2 - WindowBig.bigViewHeight / 2; bigWindowParams.type = LayoutParams.TYPE_PHONE; bigWindowParams.format = PixelFormat.RGBA_8888; bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP; bigWindowParams.width = WindowBig.bigViewWidth; bigWindowParams.height = WindowBig.bigViewHeight; } 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(getUsePercent(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 getUsePercent(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; } }
/****************************************************************************************************************************************/
使用API19,需要开启2个权限。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.rust.floatwindowdemo" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.GET_TASKS"/> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".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>
/****************************************************************************************************************************************/