一,WindowManager
1,想要实现悬浮窗,只需要几行代码就能搞定。这段代码就是把一个按钮添加到屏幕坐标为(100,100)的位置。
public class MainActivity extends AppCompatActivity {
private Button mButton;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWindowManager = getWindowManager();
mButton = new Button(this);
mButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 100;
mWindowManager.addView(mButton, mLayoutParams);
}
}
WindowManager.LayoutParams中的flags和type参数比较重要
Flags参数表示Window的属性分别为:
- FLAG_NOT_FOCUSABLE:Window不需要获取焦点,也不需要接受各种输入事件,此标记会同事启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的有焦点的Window。
- FLAG_NOT_TOUCH_MODAL:系统会把当前Window区域以外的单击事件传递给底层Window,Window区域内的单击事件自己处理。
- FLAG_SHOW_WHEN_LOCKED:Window可以显示在锁屏界面上。
Type表示Window的类型分别为:
(Window是分层级的,每个Window都有对应的z-ordered,层级大的会在最上层。)
- 应用Window:应用类Window对应着一个Activity。层级范围 1~99
- 子Window:子Window不能单独存在,它需要依附在父Window之中,比如Dialog就是一个子Window。层级范围 1000~1999
- 系统Window:系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏。层级范围 2000~2999
2,WindowManager常用的有三个方法,addView(),updateViewLayout(),removeView()。
如果需要显示的View,随着滑动跟随一起移动这就用到了updateViewLayout()。我们可以给mButton 添加setOnTouchListener(this)
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mButton, mLayoutParams);
break;
}
return false;
}
二,悬浮框的实现
package com.efrobot.launcher.newlauncher.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;
public class FloatWindow {
private volatile static FloatWindow mInstance;
private WindowManager.LayoutParams mLayoutParams;
private DisplayMetrics mDisplayMetrics;
private WindowManager mWindowManager;
private Context mContext;
private View mContentView;
private static final int WHAT_HIDE = 0x275;
private static final float DISTANCE = 15.0f;
private float offsetX, offsetY;
private long lastTouchTimeMillis;
private long downTimeMillis;
private boolean mIsShowing;
private float downX, downY;
private float oldX, oldY;
private boolean isOpen;
private View mFloatView, mPlayerView;
public FloatWindow(Context context) {
this(context, null, null);
}
public static FloatWindow getInstance(Context context) {
if (mInstance == null) {
synchronized (FloatWindow.class) {
if (mInstance == null) {
mInstance = new FloatWindow(context);
}
}
}
return mInstance;
}
/**
* 传入要显示的View
*
* @param context
* @param contentView 点击悬浮框显示的内容
* @param floatView 悬浮框
*/
public FloatWindow(Context context, View contentView, View floatView) {
this.mContext = context;
setFloatView(floatView);
setOpenView(contentView);
initWindowManager();
initLayoutParams();
}
/**
* 传入悬浮框View
*
* @param floatView
*/
public void setFloatView(View floatView) {
if (floatView != null) {
this.mFloatView = floatView;
if (isShowing()) {
getWindowManager().removeView(mContentView);
createContentView(floatView);
getWindowManager().addView(mContentView, getLayoutParams());
updateLocation(getDisplayMetrics().widthPixels / 2, getDisplayMetrics().heightPixels / 2, true);
} else {
createContentView(floatView);
}
}
}
/**
* 传入内容View
*
* @param contentView
*/
public void setOpenView(View contentView) {
if (contentView != null) {
BackgroundView backgroundView = new BackgroundView(getContext());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 40;
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP | RelativeLayout.CENTER_HORIZONTAL);
contentView.setOnTouchListener(new TouchIntercept());
contentView.setLayoutParams(layoutParams);
backgroundView.addView(contentView);
this.mPlayerView = backgroundView;
}
}
/**
* 设置窗口当前布局
*/
private void setContentView(View floatView) {
if (floatView != null) {
if (isShowing()) {
getWindowManager().removeView(mContentView);
createContentView(floatView);
getWindowManager().addView(mContentView, getLayoutParams());
updateLocation(getDisplayMetrics().widthPixels / 2, getDisplayMetrics().heightPixels / 2, true);
} else {
createContentView(floatView);
}
}
}
private void createContentView(View contentView) {
this.mContentView = contentView;
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); // 主动计算视图View的宽高信息
offsetY = getStatusBarHeight(getContext()) + contentView.getMeasuredHeight() / 2;
offsetX = contentView.getMeasuredWidth() / 2;
contentView.setOnTouchListener(new WindowTouchListener());
}
/**
* 获得上下文信息
*/
public Context getContext() {
return this.mContext;
}
public WindowManager getWindowManager() {
if (mWindowManager == null) {
mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
/**
* 获得WindowManager.LayoutParams参数
*/
public WindowManager.LayoutParams getLayoutParams() {
if (mLayoutParams == null) {
mLayoutParams = new WindowManager.LayoutParams();
initLayoutParams();
}
return mLayoutParams;
}
/**
* 获得显示信息
*/
public DisplayMetrics getDisplayMetrics() {
if (mDisplayMetrics == null) {
mDisplayMetrics = getContext().getResources().getDisplayMetrics();
}
return mDisplayMetrics;
}
/**
* 获得当前视图
*/
public View getContentView() {
return mContentView;
}
/**
* 初始化窗口管理器
*/
private void initWindowManager() {
mWindowManager = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
mDisplayMetrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(mDisplayMetrics);
}
/**
* 初始化WindowManager.LayoutParams参数
*/
private void initLayoutParams() {
getLayoutParams().flags = getLayoutParams().flags
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
getLayoutParams().dimAmount = 0.2f;
getLayoutParams().type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
getLayoutParams().height = WindowManager.LayoutParams.WRAP_CONTENT;
getLayoutParams().width = WindowManager.LayoutParams.WRAP_CONTENT;
getLayoutParams().gravity = Gravity.LEFT | Gravity.TOP;
getLayoutParams().format = PixelFormat.RGBA_8888;
getLayoutParams().alpha = 1.0f; // 设置整个窗口的透明度
offsetX = 0;
offsetY = getStatusBarHeight(getContext());
getLayoutParams().x = (int) (mDisplayMetrics.widthPixels - offsetX);
getLayoutParams().y = (int) (mDisplayMetrics.heightPixels * 1 / 4 - offsetY);
}
/**
* 获取状态栏的高度
*/
public int getStatusBarHeight(Context context) {
int height = 0;
int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
height = context.getResources().getDimensionPixelSize(resId);
}
return height;
}
/**
* 更新窗口的位置
*/
private void updateLocation(float x, float y, boolean offset) {
if (getContentView() != null) {
if (offset) {
getLayoutParams().x = (int) (x - offsetX);
getLayoutParams().y = (int) (y - offsetY);
} else {
getLayoutParams().x = (int) x;
getLayoutParams().y = (int) y;
}
getWindowManager().updateViewLayout(mContentView, getLayoutParams());
}
}
/**
* 显示窗口
*/
public void show() {
if (getContentView() != null && !isShowing()) {
getWindowManager().addView(getContentView(), getLayoutParams());
mIsShowing = true;
if (!isOpen) {
handler.sendEmptyMessage(WHAT_HIDE);
}
}
}
/**
* 隐藏当前显示窗口
*/
public void dismiss() {
if (getContentView() != null && isShowing()) {
handler.removeMessages(WHAT_HIDE);
getWindowManager().removeView(getContentView());
mIsShowing = false;
}
}
/**
* 判断当前是否有显示窗口
*/
public boolean isShowing() {
return mIsShowing;
}
private ValueAnimator alignAnimator(float x, float y) {
ValueAnimator animator = null;
if (x <= getDisplayMetrics().widthPixels / 2) {
animator = ValueAnimator.ofObject(new PointEvaluator(), new Point((int) x, (int) y), new Point(0, (int) y));
} else {
animator = ValueAnimator.ofObject(new PointEvaluator(), new Point((int) x, (int) y), new Point(getDisplayMetrics().widthPixels, (int) y));
}
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
updateLocation(point.x, point.y, true);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
lastTouchTimeMillis = System.currentTimeMillis();
handler.sendEmptyMessage(WHAT_HIDE);
}
});
animator.setDuration(160);
return animator;
}
/**
*
* */
public class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object from, Object to) {
Point startPoint = (Point) from;
Point endPoint = (Point) to;
float x = startPoint.x + fraction * (endPoint.x - startPoint.x);
float y = startPoint.y + fraction * (endPoint.y - startPoint.y);
Point point = new Point((int) x, (int) y);
return point;
}
}
/**
* 打开选项菜单
*/
public void showPlayer() {
if (isOpen) {
return;
}
getLayoutParams().height = WindowManager.LayoutParams.MATCH_PARENT;
getLayoutParams().width = WindowManager.LayoutParams.MATCH_PARENT;
oldX = getLayoutParams().x;
oldY = getLayoutParams().y;
Log.i("FloatWindow", "showPlayer: oldX=" + oldX + ",oldY=" + oldY);
setContentView(mPlayerView);
handler.removeMessages(WHAT_HIDE);
isOpen = true;
}
/**
* 关闭选项菜单
*/
public void turnMini() {
if (!isOpen) {
return;
}
isOpen = false;
getLayoutParams().flags &= ~(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);// 取消WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性
getLayoutParams().flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;//重新设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性
getLayoutParams().height = WindowManager.LayoutParams.WRAP_CONTENT;
getLayoutParams().width = WindowManager.LayoutParams.WRAP_CONTENT;
setContentView(mFloatView);
getLayoutParams().alpha = 1.0f;
updateLocation(oldX, oldY, false);
lastTouchTimeMillis = System.currentTimeMillis();
handler.sendEmptyMessage(WHAT_HIDE);
}
/**
* 带有按键监听事件和触摸事件的BackgroundView
*/
class BackgroundView extends RelativeLayout {
public BackgroundView(Context context) {
super(context);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
turnMini();
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
turnMini();
return true;
case MotionEvent.ACTION_OUTSIDE:
turnMini();
return true;
}
return super.onTouchEvent(event);
}
}
class TouchIntercept implements View.OnTouchListener {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
lastTouchTimeMillis = System.currentTimeMillis();
break;
}
return true;
}
}
class WindowTouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!isOpen) {
down(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (!isOpen) {
move(event);
}
break;
case MotionEvent.ACTION_UP:
if (!isOpen) {
up(event);
}
break;
case MotionEvent.ACTION_OUTSIDE:
if (isOpen) {
turnMini();
return true;
}
break;
default:
break;
}
return false;
}
}
/**
* 按下事件处理
*/
private void down(MotionEvent event) {
downX = event.getRawX();
downY = event.getRawY();
getLayoutParams().alpha = 1.0f;
downTimeMillis = System.currentTimeMillis();
lastTouchTimeMillis = System.currentTimeMillis();
getWindowManager().updateViewLayout(getContentView(), getLayoutParams());
}
/**
* 移动事件处理
*/
private void move(MotionEvent event) {
lastTouchTimeMillis = System.currentTimeMillis();
updateLocation(event.getRawX(), event.getRawY(), true);
}
/**
* 抬起事件处理
*/
private void up(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
if (x >= downX - DISTANCE && x <= downX + DISTANCE && y >= downY - DISTANCE && y <= downY + DISTANCE) {
if (System.currentTimeMillis() - downTimeMillis > 1200) {
// 长按
} else {
showPlayer(); //点击
}
} else {
ValueAnimator animator = alignAnimator(x, y);
animator.start();
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT_HIDE:
if (System.currentTimeMillis() - lastTouchTimeMillis >= 3500) {
if (!isOpen) {
getLayoutParams().alpha = 0.4f;
getWindowManager().updateViewLayout(getContentView(), getLayoutParams());
}
} else {
if (isOpen) {
lastTouchTimeMillis = System.currentTimeMillis() + 3500;
}
handler.sendEmptyMessageDelayed(WHAT_HIDE, 200);
}
break;
}
}
};
}
调用起来就比较简单了,比如我们可以在service中这样写
mFloatWindow =FloatWindow.getInstance(this);
mFloatWindow.setFloatView(mFloatV);//传入悬浮框View
mFloatWindow.setOpenView(mMenuV);//传入要打开的View
mFloatWindow.show();