当我们在手机上安装360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程。如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失。悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManager(Window的内部类)。

通过WindowManager的addView()方法,并设置WindowManager.LayoutParams的相关属性,就可以往WindowManager中加入所需要的View,而根据WindowManager.LayoutParams属性不同,也就能实现不同的效果。比如创建系统顶级窗口,实现悬浮窗口效果。如果需要将View从WindowManager中移除,只需要调用removeView()即可。

1、代码实现主界面为一个Button按钮点击跳转到小悬浮窗口,然后关闭本窗口。

package com.example.suspend;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity {
private Button suspend;
private TextView text; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initUI();
} private void initUI() {
// TODO Auto-generated method stub
// WindowService wind = new WindowService();
suspend = (Button)findViewById(R.id.suspend);
suspend.setOnClickListener(new suspendListener());
text = (TextView)findViewById(R.id.text);
text.setText(MyWindowManager.getUsedPercentValue(getApplicationContext()));
}
public class suspendListener implements OnClickListener{ @Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
//启动悬浮窗口关闭本窗口
Intent intent = new Intent(MainActivity.this,WindowService.class);
startService(intent);
finish();
}
} }

2、WindowService 中使用了一个定时器,定时为500ms,在定时器里创建小窗口,在启动前先判断是否在桌面

/**
* 判断当前界面是否桌面
*/
private boolean isHome(){
ActivityManager mactivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks();
return getHomes().contains(rti.get().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);
System.out.println("packageName" + names);
Log.d(TAG,"tag:"+names);
}
return names;
}

完整代码为:

package com.example.suspend;

import android.app.ActivityManager;
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;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask; public class WindowService extends Service {
private static final String TAG = "PACKAGENAME";
//用于线程中创建或移除悬浮窗。
private Handler handler = new Handler();
//定时器,定时进行检测当前应该创建还是移除悬浮
private Timer timer;
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
} /**
* 启动service的时候,onCreate方法只有第一次会调用,onStartCommand和onStart每次都被调用。
* onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止
* 这个整形可以有四个返回值:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/**
* 开启定时器,每隔500ms刷新一次
*/
if (timer == null){
timer = new Timer();
timer.scheduleAtFixedRate(new RefreshTask(),,);
}
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<ActivityManager.RunningTaskInfo> rti = mactivityManager.getRunningTasks();
return getHomes().contains(rti.get().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);
System.out.println("packageName" + names);
Log.d(TAG,"tag:"+names);
}
return names;
}
}

3、创建窗口方法具体代码有写

public static void createSmallWindow(Context context){
//WindowManager基本用到:addView,removeView,updateViewLayout
WindowManager windowManager = getWindowManager(context);
//获取屏幕宽高 abstract Display getDefaultDisplay(); //获取默认显示的 Display 对象
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight(); //设置小悬浮窗口的位置以及相关参数
if (smallWindowActivity == null) {
smallWindowActivity = new SmallWindowActivity(context);
if (smallWindowParams == null) {
smallWindowParams = new LayoutParams();//
smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
smallWindowParams.height = SmallWindowActivity.viewHeight;
smallWindowParams.x = screenWidth;//设置悬浮窗口位置
smallWindowParams.y = screenHeight / ;
}
smallWindowActivity.setParams(smallWindowParams);
windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
}
}

要移除窗口可使用 windowManager.removeView(smallWindowActivity);

小窗口实现的是显示手机内存百分比,下面为计算内存百分比的方法:

/**
* 计算已使用内存的百分比,并返回。
*
* @param context
* 可传入应用程序上下文。
* @return 已使用内存的百分比,以字符串形式返回。
*/
public static String getUsedPercentValue(Context context) {
String dir = "/proc/meminfo";
try {
FileReader fr = new FileReader(dir);
BufferedReader br = new BufferedReader(fr, );
String memoryLine = br.readLine();
String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
br.close();
long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
long availableSize = getAvailableMemory(context) / ;
int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * );
return percent + "%";
} catch (IOException e) {
e.printStackTrace();
}
return "悬浮窗";
} /**
* 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
*
* @param context
* 可传入应用程序上下文。
*/
public static void updateUsedPercent(Context context) {
if (smallWindowActivity != null) {
TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
percentView.setText(getUsedPercentValue(context));
}
}
/**
* 获取当前可用内存,返回数据以字节为单位。
*
* @param context
* 可传入应用程序上下文。
* @return 当前可用内存。
*/
private static long getAvailableMemory(Context context) {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
getActivityManager(context).getMemoryInfo(mi);
return mi.availMem;
} /**
* 如果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;
}

完整的MyWindowManager类代码为

package com.example.suspend;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager;
import android.widget.TextView; import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException; public class MyWindowManager{
//小悬浮窗View的实例
private static SmallWindowActivity smallWindowActivity; //大悬浮窗View的实例
private static BigWindowActivity bigWindowActivity; //小悬浮View的参数
private static LayoutParams smallWindowParams; //大悬浮View的参数
private static LayoutParams bigWindowParams; //用于控制在屏幕上添加或移除悬浮窗
private static WindowManager mWindowManager; //用于获取手机可用内存
private static ActivityManager mactivityManager; public static void createSmallWindow(Context context){
//WindowManager基本用到:addView,removeView,updateViewLayout
WindowManager windowManager = getWindowManager(context);
//获取屏幕宽高 abstract Display getDefaultDisplay(); //获取默认显示的 Display 对象
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight(); //设置小悬浮窗口的位置以及相关参数
if (smallWindowActivity == null) {
smallWindowActivity = new SmallWindowActivity(context);
if (smallWindowParams == null) {
smallWindowParams = new LayoutParams();//
smallWindowParams.type = LayoutParams.TYPE_PHONE;//设置窗口的window type
smallWindowParams.format = PixelFormat.RGBA_8888;//设置图片格式,效果为背景透明
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;//下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;//调整悬浮窗口位置在左边中间
smallWindowParams.width = SmallWindowActivity.viewWidth;//设置悬浮窗口的宽高
smallWindowParams.height = SmallWindowActivity.viewHeight;
smallWindowParams.x = screenWidth;//设置悬浮窗口位置
smallWindowParams.y = screenHeight / ;
}
smallWindowActivity.setParams(smallWindowParams);
windowManager.addView(smallWindowActivity, smallWindowParams);//将需要加到悬浮窗口中的View加入到窗口中
}
} /**
* 创建一个大悬浮窗。位置为屏幕正中间。
*
* @param context
* 必须为应用程序的Context.
*/
// @SuppressWarnings("deprecation")
public static void createBigWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (bigWindowActivity == null) {
bigWindowActivity = new BigWindowActivity(context);
if (bigWindowParams == null) {
bigWindowParams = new LayoutParams();
bigWindowParams.x = screenWidth / - BigWindowActivity.viewWidth / ;
bigWindowParams.y = screenHeight / - BigWindowActivity.viewHeight / ;
bigWindowParams.type = LayoutParams.TYPE_PHONE;
bigWindowParams.format = PixelFormat.RGBA_8888;
bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
bigWindowParams.width = BigWindowActivity.viewWidth;
bigWindowParams.height = BigWindowActivity.viewHeight;
}
windowManager.addView(bigWindowActivity, bigWindowParams);
}
} /**
* 将小悬浮窗从屏幕上移除。
* abstract void removeViewImmediate(View view);//是removeView(View) 的一个特殊扩展,
* 在方法返回前能够立即调用该视图层次的View.onDetachedFromWindow() 方法。
* @param context
* 必须为应用程序的Context.
*/
public static void removeSmallWindow(Context context) {
if (smallWindowActivity != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(smallWindowActivity);//移除悬浮窗口
smallWindowActivity = null;
}
} /**
* 将大悬浮窗从屏幕上移除。
*
* @param context
* 必须为应用程序的Context.
*/
public static void removeBigWindow(Context context) {
if (bigWindowActivity != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(bigWindowActivity);
bigWindowActivity = null;
}
} /**
* 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
*
* @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
*/
public static boolean isWindowShowing() {
return smallWindowActivity != null || bigWindowActivity != null;
//return smallWindowActivity != 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;
} /**
* 计算已使用内存的百分比,并返回。
*
* @param context
* 可传入应用程序上下文。
* @return 已使用内存的百分比,以字符串形式返回。
*/
public static String getUsedPercentValue(Context context) {
String dir = "/proc/meminfo";
try {
FileReader fr = new FileReader(dir);
BufferedReader br = new BufferedReader(fr, );
String memoryLine = br.readLine();
String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
br.close();
long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
long availableSize = getAvailableMemory(context) / ;
int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * );
return percent + "%";
} catch (IOException e) {
e.printStackTrace();
}
return "悬浮窗";
} /**
* 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
*
* @param context
* 可传入应用程序上下文。
*/
public static void updateUsedPercent(Context context) {
if (smallWindowActivity != null) {
TextView percentView = (TextView) smallWindowActivity.findViewById(R.id.percent);
percentView.setText(getUsedPercentValue(context));
}
}
/**
* 获取当前可用内存,返回数据以字节为单位。
*
* @param context
* 可传入应用程序上下文。
* @return 当前可用内存。
*/
private static long getAvailableMemory(Context context) {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
getActivityManager(context).getMemoryInfo(mi);
return mi.availMem;
} /**
* 如果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;
} }

4、小窗口的代码继承了LinearLayout,可实现手动触摸移动,分别设置了手指点击下、移动和离开的处理。实现代码为:

    @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) {
MyWindowManager.createBigWindow(getContext());//创建大窗口
MyWindowManager.removeSmallWindow(getContext());//移除小窗口
Toast.makeText(getContext(), "手指离开屏幕!", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
return true;
}

完整代码:

package com.example.suspend;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.view.WindowManager; import java.lang.reflect.Field; public class SmallWindowActivity 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;//记录手指按下时在屏幕上的纵坐标的值 private float xInView;//记录手指按下时在小悬浮窗的View上的横坐标的值
private float yInView;//记录手指按下时在小悬浮窗的View上的纵坐标的值
/**
*
* @param context
*/
public SmallWindowActivity(Context context) {
super(context);
windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(context).inflate(R.layout.activity_small_window,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) {
MyWindowManager.createBigWindow(getContext());//创建大窗口
MyWindowManager.removeSmallWindow(getContext());//移除小窗口
Toast.makeText(getContext(), "手指离开屏幕!", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
return true;
} //将悬浮窗的参数传入,用于更新小悬浮窗的位置
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);
} /**
* 用于获取状态栏高度
* @return 返回状态栏高度的像素值
*/
private int getStatusBarHeight() {
if (statusBarHeight == ) {
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;
}
}

小窗口仅实现了显示内存百分比,添加了一个TextView,小窗口的布局代码为:

<?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>

5、当点击小窗口的时候添加了监听就是当手指离开的时候启动创建一个另一个窗口然后关闭本窗口。

大窗口也是实现点击可以随意移动,还添加了两个按钮的功能,发送短信和返回。实现代码为:

package com.example.suspend;

import android.content.Context;
import android.content.Intent;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout; public class BigWindowActivity extends LinearLayout { //记录大悬浮窗的宽度
public static int viewWidth; //记录大悬浮窗高度
public static int viewHeight; private Button phone,sms,app,music;
int screenWidth,screenHeight;
int lastX,lastY;//记录移动的最后的位置
int dx,dy; public BigWindowActivity(final Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.big_window, this);
View view = findViewById(R.id.big_window_layout);
viewWidth = view.getLayoutParams().width;
viewHeight = view.getLayoutParams().height; initUI(); } private void initUI() {
// TODO Auto-generated method stub
//获取屏幕的分辨率
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels-;
Button back = (Button)findViewById(R.id.fanhui);
//添加触摸监听
back.setOnTouchListener(new OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
//获取Action
int ea = event.getAction();
Log.i("TAG","Touch:"+ea);
switch (ea){
case MotionEvent.ACTION_DOWN:
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动中动态设置位置
mobilesetting(v,event);
break;
case MotionEvent.ACTION_UP://当手指离开的时候执行
if (dx==dy){
comeback(getContext());
}
break;
}
return false;
}
}); sms = (Button)findViewById(R.id.SMS);
sms.setOnTouchListener(new OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
int ea = event.getAction();
Log.i("TAG","Touch:"+ea);
switch (ea){
case MotionEvent.ACTION_DOWN:
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动中动态设置位置
mobilesetting(v,event);
break;
case MotionEvent.ACTION_UP://当手指离开的时候执行
if (dx==dy){
sms();
}
break;
} return false;
}
});
}
/**
* 点击返回移除大窗口创建小窗口
* @param context
*/
public void comeback(Context context){
// 点击返回的时候,移除大悬浮窗,创建小悬浮窗
MyWindowManager.removeBigWindow(context);
MyWindowManager.createSmallWindow(context);
}
//发短信
public void sms(){
Intent it = new Intent(Intent.ACTION_VIEW);
it.putExtra("sms_body", "The SMS text");
it.setType("vnd.android-dir/mms-sms");
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(it);
comeback(getContext());
} /**
* 移动控件设置位置
* @param v
* @param event
*/
public void mobilesetting(View v,MotionEvent event){
//移动中动态设置位置
dx = (int)event.getRawX()-lastX;//移动中x当前位置
dy = (int)event.getRawY()-lastY; int left = v.getLeft()+dx;
int top = v.getTop()+dy;
int right = v.getRight()+dx;
int bottom = v.getBottom()+dy; if(left<){
left=;
right = left+v.getWidth();//
}
if (right>screenWidth){
right = screenWidth;
left = right - v.getWidth();//max
}
if(top < ){
top = ;
bottom = top + v.getHeight();
}
if(bottom > screenHeight){
bottom = screenHeight;
top = bottom - v.getHeight();
}
v.layout(left, top, right, bottom);
//将当前的位置再次设置
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
}
// @Override
// public boolean onTouchEvent(MotionEvent event) {
// // TODO Auto-generated method stub
// //点击屏幕弹出的框会消失
// //popupWindow.dismiss();
// return super.onTouchEvent(event);
// }
}

布局代码为四个按钮控件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/big_window_layout"
> <Button
android:layout_marginTop="200dip"
android:layout_marginLeft="150dip"
android:id="@+id/fanhui"
android:layout_width="@dimen/bigwidth"
android:layout_height="@dimen/bigheight"
android:textColor="#ffffff"
android:textSize="@dimen/bigtextsize"
android:background="@xml/shape"
android:text="@string/rt"
/> <Button
android:id="@+id/app"
android:layout_width="@dimen/bigwidth"
android:layout_height="@dimen/bigheight"
android:layout_above="@+id/music"
android:layout_alignLeft="@+id/fanhui"
android:background="@xml/shape"
android:textSize="@dimen/bigtextsize"
android:text="@string/appone" /> <Button
android:id="@+id/music"
android:layout_width="@dimen/bigwidth"
android:layout_height="@dimen/bigheight"
android:layout_alignBaseline="@+id/fanhui"
android:layout_alignBottom="@+id/fanhui"
android:layout_toLeftOf="@+id/app"
android:textSize="@dimen/bigtextsize"
android:text="@string/mu"
android:background="@xml/shape"
/> <Button
android:id="@+id/SMS"
android:layout_width="@dimen/bigwidth"
android:layout_height="@dimen/bigheight"
android:layout_below="@+id/fanhui"
android:layout_toRightOf="@+id/music"
android:textSize="@dimen/bigtextsize"
android:background="@xml/shape"
android:text="@string/sendsms" /> <Button
android:id="@+id/Phone"
android:layout_width="@dimen/bigwidth"
android:layout_height="@dimen/bigheight"
android:layout_alignBaseline="@+id/fanhui"
android:layout_alignBottom="@+id/fanhui"
android:textSize="@dimen/bigtextsize"
android:layout_toRightOf="@+id/fanhui"
android:background="@xml/shape"
android:text="@string/callphone" /> </RelativeLayout>

控件的实现效果是在xml里面添加了shape.xml,设置了控件的颜色跟形状。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 实心 -->
<solid android:color="#ff9d77"/>
<!-- 渐变 -->
<gradient
android:startColor="#ff8c00"
android:endColor="#FFFFFF"
android:angle="" />
<!-- 描边 -->
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<!-- 圆角 -->
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>

6、使用到的权限为:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.suspend"
android:versionCode=""
android:versionName="1.0" > <uses-sdk
android:minSdkVersion=""
android:targetSdkVersion="" />
<!-- 添加悬浮窗口权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.suspend.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=".WindowService"></service>
</application> </manifest>

实现的效果图:


代码下载地址:点击打开链接

Android 类似360悬浮窗口实现源码的更多相关文章

  1. 50个Android开发人员必备UI效果源码[转载]

    50个Android开发人员必备UI效果源码[转载] http://blog.csdn.net/qq1059458376/article/details/8145497 Android 仿微信之主页面 ...

  2. [转载] 50个Android开发人员必备UI效果源码

    好东西,多学习! Android 仿微信之主页面实现篇Android 仿微信之界面导航篇Android 高仿QQ 好友分组列表Android 高仿QQ 界面滑动效果Android 高仿QQ 登陆界面A ...

  3. 编译Android 4.4.4 r1的源码刷Nexus 5手机详细教程

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/54562606 网上关于编译Android源码的教程已经很多了,但是讲怎么编译And ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. Android版的菜谱客户端应用源码完整版

    Android版的菜谱客户端应用源码完整版,这个文章是从安卓教程网转载过来的,不是本人的原创,希望能够帮到大家的学习吧. <ignore_js_op> 152936qc7jdnv6vo0c ...

  8. android浪漫樱花凋零动态壁纸应用源码

    android浪漫樱花凋零动态壁纸应用源码,是从那个安卓教程网拿过来的,本项目是一套基于安卓的樱花动态壁纸项目源码,安装以后桌面没有图标,但是可以在修改壁纸-动态壁纸中找到.我的分辨率是480×854 ...

  9. Android进阶:五、RxJava2源码解析 2

    上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...

随机推荐

  1. android studio在windows上设置git/ssh

    windows果然是与众不同的,凡事都要那么麻烦一点点(当然..是对程序员来说..) 一开始,我想用cygwin里的git,就省得我再多装一套软件,配置也可以统一,但事实证明不行 在android s ...

  2. 15.Nginx 解析漏洞复现

    Nginx 解析漏洞复现 Nginx解析漏洞复现. 版本信息: Nginx 1.x 最新版 PHP 7.x最新版 由此可知,该漏洞与Nginx.php版本无关,属于用户配置不当造成的解析漏洞. 使用d ...

  3. windows配置换行符

    CR.LF.CR/LF为不同操作系统上使用的换行符: Windows/DOS系统:采用CR/LF表示下一行: Unix/Linux系统:采用LF表示下一行: Mac OS系统:采用CR表示下一行: M ...

  4. Codeforces - 102222C - Caesar Cipher

    https://codeforc.es/gym/102222/my 好像在哪里见过这个东西?字符的左右移还是小心,注意在mod26范围内. #include<bits/stdc++.h> ...

  5. 为什么要使用func.call(this)

    1.为什么要使用func.call(this) 在正常模式下,js 函数里那些你没有声明就使用的变量,其实是访问的全局对象的属性.但是在严格模式下,不允许这种语法,所有变量都必须要显示声明,所以如果你 ...

  6. 强联通分量之kosaraju算法

    首先定义:强联通分量是有向图G=(V, E)的最大结点集合,满足该集合中的任意一对结点v和u,路径vu和uv同时存在. kosaraju算法用来寻找强联通分量.对于图G,它首先随便找个结点dfs,求出 ...

  7. 整合spring和hibernate框架

    一)整合spring和hibernate框架整合要点:(1)数据源配置在Spring的配置文件中,供Spring和Hibernate框架共同使用:(2)不再需要hibernate.hbm.xml配置文 ...

  8. 通过jQuery实现AJAX

    通过jQuery实现AJAX > 使用get和getJSON都会有缓存问题,并且使用get方法不能传送较多的数据. 问题: 在IE浏览器中,get请求使用ajax存在缓存问题,会使用上一次请求的 ...

  9. c# Equals对比忽略大小写

    String.Equals(str1,str2,StringComparison.CurrentCultureIgnoreCase); StringComparison.CurrentCultureI ...

  10. BZOJ 4264 小C找朋友 哈希+脑子

    好吧我觉得是脑子,别人觉得是套路$qwq$ 这道题相当于是求除了$u,v$两点互相连接,所连的点相同的点对$(u,v)$ 我们首先每个点一个随机权值,对于$u$点记为$w[u]$,然后记与$u$点相连 ...