概述
Android项目开发时,有时候需要开发一些悬浮在桌面上的视图。比如桌面小精灵,各种音乐播放器的悬浮播放控制栏等等。本文就借助一个小的demo,用代码的方式大概进行介绍。
原理
开发桌面悬浮窗口一般遵循两个大的原则。
1.最根本的原则是采用WindowManager类,WindowManager有addView(View view, ViewGroup.LayoutParams params)方法,改方法可以直接在Android根窗口布局中添加一个View视图。这里的view其实就是我们要在桌面上显示的视图。
同理,如果我们要让这个视图消失掉,就可以调用WindowManager的removeView(View view)方法。
2.另外一个原则是,需要借助service。因为service是没有界面的,如果在activity中进行的话,当前的界面会挡住这个悬浮框。
好了,话不多说直接上代码。
关键代码展示
需要展示的View视图的布局layout_float_window.xml
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/small_window_layout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#f4f2f3"
android:paddingLeft="@dimen/margin_20"
android:paddingRight="@dimen/margin_20">
<ImageView
android:layout_width="@dimen/margin_60"
android:layout_height="@dimen/margin_60"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_microphone_white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="70dp"
android:layout_marginRight="50dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="0sp"
android:text="咪咕家语音播报"
android:textColor="#282e33"
android:textSize="17sp" />
<TextView
android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:lineSpacingExtra="0sp"
android:text="IM消息"
android:textColor="#66737f"
android:textSize="14sp" />
</LinearLayout>
<ImageView
android:id="@+id/iv_close_notify"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical|right"
android:scaleType="center"
android:src="@drawable/ic_close_grey1" />
</FrameLayout>
需要展示的View视图类FloatWindow.java
public class FloatWindow 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上的纵坐标的值
private TextView tittleView; //标题
private TextView contentView; //内容
private ImageView closeView; //关闭按钮
public FloatWindow(final Context context) {
super(context);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(context).inflate(R.layout.layout_float_window, this);
View view = findViewById(R.id.small_window_layout);
viewWidth = view.getLayoutParams().width;
viewHeight = view.getLayoutParams().height;
contentView = (TextView) findViewById(R.id.percent);
contentView.setText("IM消息");
closeView = (ImageView) findViewById(R.id.iv_close_notify);
closeView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FloatWindowManager.removeFloatWindow(context);
Intent intent = new Intent(getContext(), VoiceBroadcastService.class);
context.stopService(intent);
}
});
}
@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:
break;
default:
break;
}
return true;
}
/**
* 将悬浮窗的参数传入,用于更新悬浮窗的位置。
*
* @param params 悬浮窗的参数
*/
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 == 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);
statusBarHeight = getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
return statusBarHeight;
}
}
Windowmanager管理类,该类是悬浮窗口的主要操作类。可以在桌面添加和删除窗口,并设置窗口必要的显示参数。FloatWindowManager.java
public class FloatWindowManager {
private static FloatWindow mFloatWindow; //悬浮窗View的实例
private static LayoutParams mWindowLayoutParams; //悬浮窗View的参数
private static WindowManager mWindowManager; //用于控制在屏幕上添加或移除悬浮窗
/**
* 创建一个悬浮窗
* 初始位置为屏幕的右部中间位置。
* @param context 必须为应用程序的Context.
*/
public static void createFloatWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (mFloatWindow == null) {
mFloatWindow = new FloatWindow(context);
if (mWindowLayoutParams == null) {
mWindowLayoutParams = new LayoutParams();
mWindowLayoutParams.type = LayoutParams.TYPE_PHONE;
mWindowLayoutParams.format = PixelFormat.RGBA_8888;
mWindowLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mWindowLayoutParams.width = FloatWindow.viewWidth;
mWindowLayoutParams.height = FloatWindow.viewHeight;
mWindowLayoutParams.x = screenWidth;
mWindowLayoutParams.y = screenHeight / 2;
}
mFloatWindow.setParams(mWindowLayoutParams);
//最为关键的一步操作,该操作之后就可以在桌面上看到视图
windowManager.addView(mFloatWindow, mWindowLayoutParams);
}
}
/**
* 将悬浮窗从屏幕上移除。
*
* @param context 必须为应用程序的Context.
*/
public static void removeFloatWindow(Context context) {
if (mFloatWindow != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(mFloatWindow);
mFloatWindow = null;
}
}
/**
* 更新悬浮窗文字显示
* @param context
* @param tittle
* @param content
*/
public static void updateFloatShow(Context context,String tittle,String content) {
if (mFloatWindow != null) {
TextView percentView = (TextView) mFloatWindow.findViewById(R.id.percent);
percentView.setText("IM消息");
}
}
public static boolean isWindowShowing() {
return mFloatWindow != null;
}
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
}
具体测操作service类FloatWindowService.class
public class FloatWindowService extends Service {
private Timer mTimer;
private Handler mHandler = new Handler();
@Override
public void onCreate() {
super.onCreate();
if (mTimer == null) {
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mTimer == null) {
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
mTimer.cancel();
mTimer = null;
super.onDestroy();
}
class RefreshTask extends TimerTask {
/**
* 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗
* 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗
* 当前界面是桌面,且有悬浮窗显示,设置悬浮窗内容
*/
@Override
public void run() {
if (WindowUtils.getInstance().isAtHome(getApplicationContext()) && !FloatWindowManager.isWindowShowing()) {
mHandler.post(new Runnable() {
@Override
public void run() {
FloatWindowManager.createFloatWindow(getApplicationContext());
}
});
} else if (!WindowUtils.getInstance().isAtHome(getApplicationContext()) && FloatWindowManager.isWindowShowing()) {
mHandler.post(new Runnable() {
@Override
public void run() {
FloatWindowManager.removeFloatWindow(getApplicationContext());
}
});
}
}
}
}
如果要开启android悬浮窗口就激活以上service即可。大致如下
Intent intent = new Intent(....this, FloatWindow.class);
VoiceBroadcastService.voiceUrl = voicePath;
startService(intent);
以上就是android桌面悬浮窗口的大致操作,希望能给大家带来帮助。另外,这里小小留一个悬念,就是FloatWindowService类中的isAtHome方法。这是判断当前界面是否为桌面的方法,需要的朋友可以访问Android判断当前界面是否为桌面进行查看。
另外需要特别注意的是WindowManager如果要调用.addView方法,需要为应用添加ACTION_MANAGE_OVERLAY_PERMISSION权限。否则系统会闪退,请各位格外注意。具体的添加方式这里不再多说了。