之前的项目中写了个音乐播放器,这里记录下悬浮窗相关的东西。
废话不多说,先上个效果图
1、自定义悬浮框布局
根据效果图可以看到悬浮窗 布局由一张封面图,一个圆形进度条以及4个按钮组成。
布局内容就不写了,很简单。
悬浮窗控件 :FloatLayout继承自FrameLayout,引入布局文件。
初始化 windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(dm);
//获取屏幕的宽度,用于后边计算吸边操作
screenWidth = dm.widthPixels;
然后通过 onInterceptTouchEvent 去处理按下、移动和抬起事件的分发拦截。这个是悬浮窗点击、移动的重点
刚开始打算是用onTouchEvent去处理触摸事件,经过验证发现并不能解决父布局和子View的点击冲突,于是试用了onInterceptTouchEvent 方法,发现这才是解决这里点击冲突正确的姿势。
public boolean onInterceptTouchEvent(MotionEvent ev) {
//左上角为原点,获取相对屏幕的坐标
x = (int) ev.getRawX();
y = (int) ev.getRawY();
//区分拖动和点击事件 ,判断是否需要拦截
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//获取相对于父view的坐标点
mTouchStartX = ev.getX();
mTouchStartY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//处理移动逻辑
float moveStartX = ev.getX();
float moveStartY = ev.getY();
//移动的距离大于3,移动距位置
if (Math.abs(mTouchStartX - moveStartX) > 3 && Math.abs(mTouchStartY - moveStartY) > 3) {
mParams.x = (int) (x - mTouchStartX);
mParams.y = (int) (y - mTouchStartY);
mWindowManager.updateViewLayout(this, mParams);
return super.onInterceptTouchEvent(ev);
}
break;
case MotionEvent.ACTION_UP:
float endX = ev.getRawX();
//根据最终手指停留的地方,设置吸边效果
if (endX > screenWidth / 2) {
mParams.x = screenWidth - getWidth();
} else {
mParams.x = 0;
}
mWindowManager.updateViewLayout(this, mParams);
float upX = ev.getX();
float upY = ev.getY();
if (Math.abs(upX - mTouchStartX) > 30 && Math.abs(upY - mTouchStartY) > 30){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
接下来就是正常处理按钮的点击事件了。因为设计的是当 菜单按钮显示的时候,点击封面图是跳转其他页面,所以需要写一个计时器 把菜单布局收起来。并且在菜单布局收起来的时候,注意调用吸边操作
/**
* 吸边操作
*/
private void adscrop() {
if (isWindowLive){
if (x > screenWidth / 2){
mParams.x = screenWidth;
}else{
mParams.x = 0;
}
mWindowManager.updateViewLayout(this,mParams);
}
}
提供了一个悬浮窗的管理类,需要给悬浮窗定义一些公共方法
/**
* 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
* @param params
*/
public void setParams(WindowManager.LayoutParams params){
this.mParams = params;
}
/**
* 设置最大进度条
* @param maxLength
*/
public void setMaxProgress(int maxLength){
mFloatSeekbar.setMax(maxLength);
}
/**
* 更新进度条
* @param progress
*/
public void upDataProgress(int progress){
mFloatSeekbar.setProgress(progress);
}
/**
*隐藏
*/
public void hide(){
mView.setVisibility(GONE);
}
/**
* 显示
*/
public void show(){
mView.setVisibility(VISIBLE);
}
/**
* 开始/暂停
* @param isStart
*/
public void startOrStop(boolean isStart){
if (isStart){
isPlaying = true;
mIvStart.setImageResource(R.mipmap.float_image_stop);
}else{
isPlaying = false;
mIvStart.setImageResource(R.mipmap.float_image_start);
}
}
/**
* 设置悬浮窗是否被销毁,如果被销毁,移除计时器
* @param isLive
*/
public void windowState(boolean isLive){
isWindowLive = isLive;
if (isWindowLive){
handler.removeCallbacks(runnable);
}
}
2、FloatWindowManager 管理类
/**
* 创建悬浮窗
* @param context
*/
public static void creatFloatWindow(Context context){
mParams = new WindowManager.LayoutParams();
mWindowManager = getWindowManager(context);
mFloatLayout = new FloatLayout(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
//设置图片格式,透明背景效果
mParams.format = PixelFormat.RGBA_8888;
//设置不可聚焦
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//设置位置左上方
mParams.gravity = Gravity.START | Gravity.TOP;
DisplayMetrics dm = new DisplayMetrics();
//取得窗口属性
mWindowManager.getDefaultDisplay().getMetrics(dm);
//窗口的宽度
int screenWidth = dm.widthPixels;
//窗口高度
int screenHeight = dm.heightPixels;
//以屏幕左上角为原点,设置x、y初始值,相对于gravity
mParams.x = screenWidth;
mParams.y = screenHeight / 2 + screenWidth / 3;
//设置悬浮窗口长宽数据
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mFloatLayout.setParams(mParams);
mWindowManager.addView(mFloatLayout, mParams);
}
/**
* 移除悬浮窗
*/
public static void removeFloatWindowManager() {
boolean isAttach = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
isAttach = mFloatLayout.isAttachedToWindow();
}
if (mHasShown && isAttach && mWindowManager != null)
mWindowManager.removeView(mFloatLayout);
mHasShown = false;
mFloatLayout.windowState(false);
}
/**
*返回已创建的WindowManager。
* @param context
* @return
*/
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null){
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
因为设计的是音乐播放器,,所以播放功能应该放在service,悬浮窗的控制也放在service中,所以需要提供公共方法
public static void hide() {
if (mHasShown)
mFloatLayout.hide();
mHasShown = false;
}
public static void show() {
if (!mHasShown)
mFloatLayout.show();
mHasShown = true;
mFloatLayout.windowState(true);
}
public static void setMaxProgress(int length){
if (mHasShown || mFloatLayout != null)
mFloatLayout.setMaxProgress(length);
}
public static void updataProgress(int progress) {
if (mHasShown || mFloatLayout != null)
mFloatLayout.upDataProgress(progress);
}
public static void startMusic(boolean isStart){
mFloatLayout.startOrStop(isStart);
}