Android悬浮窗的简单实现
⽬录
概述
原理
Android的界⾯绘制,都是通过 WindowManager 的服务来实现的。 WindowManager 实现了 ViewManager 接⼝,可以通过获取 WINDOW_SERVICE 系统服务得到。⽽ViewManager 接⼝有 addView ⽅法,我们就是通过这个⽅法将悬浮窗控件加⼊到屏幕中去。
为了让悬浮窗与Activity脱离,使其在应⽤处于后台时悬浮窗仍然可以正常运⾏,使⽤Service来启动悬浮窗并做为其背后逻辑⽀撑。
权限
在 API Level >= 23 的时候,需要在l⽂件中声明权限 SYSTEM_ALERT_WINDOW 才能在其他应⽤上绘制控件。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
除了这个权限外,我们还需要在系统设置⾥⾯对本应⽤进⾏设置悬浮窗权限。该权限在应⽤中需要启动 Settings.ACTION_MANAGE_OVERLAY_PERMISSION 来让⽤户⼿动设置权限。
if (!Settings.canDrawOverlays(this)) {
showError("当前⽆权限,请授权");
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
} else {
startService(new Intent(MainActivity.this, FloatingService.class));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 0) {
if (!Settings.canDrawOverlays(this)) {
showError("授权失败");
} else {
showMsg("授权成功");
startService(new Intent(MainActivity.this, FloatingService.class));
}
}
}
LayoutParam
WindowManager 的 addView ⽅法有两个参数,⼀个是需要加⼊的控件对象,另⼀个参数是 WindowManager.LayoutParam 对象。
  这⾥需要着重说明的是 LayoutParam ⾥的 type 变量。这个变量是⽤来指定窗⼝类型的。在设置这个变量时,需要注意⼀个坑,那就是需要对不同版本的Android系统进⾏适配。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else {
}
实例
添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
添加Service组件
<service android:name=".MediaFloatService"/>
MyApplication
public class PubApplication extends Application {
//设置⼀个全局变量来判断悬浮窗是否已经开启
public static Boolean isMediaFloatShow = false;
}
MediaFloatService
public class MediaFloatService extends Service {
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private View mView;
private ViewFloatMediaBinding floatView;
private ArrayList<String> lstFilePaths;
private int currentIndex;
private int screenWidth;
private int screenHeight;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//加载窗⼝布局
initWindow(intent);
StartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//移除窗⼝布局
}
}
加载窗⼝布局
private void initWindow(Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
//获取WindowManager服务
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
//取得屏幕尺⼨
DisplayMetrics dm = new DisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
//设置LayoutParams
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else {
}
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_FULLSCREEN                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
layoutParams.format = PixelFormat.RGBA_8888; //背景透明效果
layoutParams.width = 512; //悬浮窗⼝长宽值,单位为 px ⽽⾮ dp
layoutParams.height = 450;
layoutParams.x = 100; //启动位置
layoutParams.y = 100;
//加载悬浮窗布局
floatView = ViewFloatMediaBinding.inflate(LayoutInflater.from(MediaFloatService.this));
mView = Root();
//mView.setAlpha((float) 0.9);
//设定悬浮窗控件
floatView.ivFloatMediaClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
MyApplication.isMediaFloatShow = false;
//切记添加关闭服务按钮事件,调⽤ stopSelf() ⽅法以关闭悬浮窗。
stopSelf();
}
});
//接收传值
Bundle bundle = Extras();
lstFilePaths = StringArrayList("lstFilePaths");
currentIndex = Int("currentIndex");
floatView.ivFloatMediaPrev.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//判断是否是第⼀个,如果是则跳到最后循环播放
if (currentIndex == 0) currentIndex = lstFilePaths.size() - 1;
else currentIndex = currentIndex - 1;
showImage();
//Glide.with(MediaFloatService.this).(currentIndex)).into(floatView.ivFloatMediaShow);                }
});
floatView.ivFloatMediaNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//判断是否是最后⼀个,如果是则跳到最前循环播放
if (currentIndex == lstFilePaths.size() - 1) currentIndex = 0;
else currentIndex = currentIndex + 1;
showImage();
//Glide.with(MediaFloatService.this).(currentIndex)).into(floatView.ivFloatMediaShow);                }
});
BitmapFactory.Options options = (currentIndex));
layoutParams.width = Math.min(options.outWidth, screenWidth); //Math.min取得两个数据中的最⼩值
layoutParams.height = Math.min(options.outHeight, screenHeight);
Glide.with(this).(currentIndex)).into(floatView.ivFloatMediaShow);
//单击事件是否显⽰控制按钮
floatView.ivFloatMediaShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (Visibility() == View.INVISIBLE)
pFloatMediaControl.setVisibility(View.INVISIBLE);
}
});
//提交布局
windowManager.addView(mView, layoutParams);
}
}
}
取得屏幕尺⼨
DisplayMetrics dm = new DisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
根据路径取得图⽚尺⼨
private BitmapFactory.Options getBitmapOptions(String filepath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
return options;
}
变更显⽰图⽚
变更图⽚时悬浮窗⼝也要根据变更后的图⽚再做调整,不然窗⼝⼤⼩还是上⼀图⽚的尺⼨⽐例private void showImage() {
BitmapFactory.Options options = (currentIndex));
layoutParams.width = Math.min(options.outWidth, screenWidth); //Math.min取得两个数据中的最⼩值
layoutParams.height = Math.min(options.outHeight, screenHeight);
Glide.with(this).(currentIndex)).into(floatView.ivFloatMediaShow);
windowManager.updateViewLayout(mView, layoutParams);
}
窗⼝拖动与缩放
窗⼝拖动
floatView.ivFloatMediaShow.setOnTouchListener(new View.OnTouchListener() {
private int dX;
private int dY;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (Action()) {
case MotionEvent.ACTION_DOWN:
dX = (int) RawX();
dY = (int) RawY();
break;
case MotionEvent.ACTION_MOVE:
int nX = (int) RawX();
int nY = (int) RawY();
int cW = nX - dX;
int cH = nY - dY;
dX = nX;
dY = nY;
layoutParams.x = layoutParams.x + cW;
layoutParams.y = layoutParams.y + cH;
windowManager.updateViewLayout(mView, layoutParams);
break;
default:
break;
}
return true;
}
});
单击双击
floatView.ivFloatMediaShow.setOnTouchListener(new View.OnTouchListener() {
private int sX;
private int sY;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (Action()) {
case MotionEvent.ACTION_DOWN:
sX = (int) RawX();
sY = (int) RawY();
break;
case MotionEvent.ACTION_UP:
//如果抬起时的位置和按下时的位置⼤致相同视作单击事件
int nX2 = (int) RawX();
int nY2 = (int) RawY();
int cW2 = nX2 - sX;
int cH2 = nY2 - sY;
//间隔值可能为负值,所以要取绝对值进⾏⽐较
if (Math.abs(cW2) < 3 && Math.abs(cH2) < 3) view.performClick();
break;
default:
break;
}
return true;
}
});
双指缩放
floatView.ivFloatMediaShow.setOnTouchListener(new View.OnTouchListener() {
private ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(MediaFloatService.this, new myScale());    @Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (Action()) {
case MotionEvent.ACTION_DOWN:
isP2Down = true; //双指按下
break;
case MotionEvent.ACTION_MOVE:
if (PointerCount() == 2) {
//双指缩放
}
break;
case MotionEvent.ACTION_UP:
isP2Down = false; //双指抬起
break;
default:
break;
}
return true;
}
});
ScaleGestureDetector
private float initSapcing = 0;
private int initWidth;
private int initHeight;
private int initX;
private int initY;
private boolean isP2Down = false;
private class myScale extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
//            CurrentSpan();//两点间的距离跨度
//            CurrentSpanX();//两点间的x距离
//            CurrentSpanY();//两点间的y距离
//            FocusX();      //
//            FocusY();      //
//            PreviousSpan(); //上次
//            PreviousSpanX();//上次
//            PreviousSpanY();//上次
//            EventTime();    //当前事件的事件
/
/            TimeDelta();    //两次事件间的时间差
//            ScaleFactor();  //与上次事件相⽐,得到的⽐例因⼦
if (isP2Down) {
//双指按下时最初间距
initSapcing = CurrentSpan();
//双指按下时窗⼝⼤⼩
initWidth = layoutParams.width;
initHeight = layoutParams.height;
//双指按下时窗⼝左顶点位置
initX = layoutParams.x;
initY = layoutParams.y;
isP2Down = false;
}
float scale = CurrentSpan() / initSapcing; //取得缩放⽐
int newWidth = (int) (initWidth * scale);
int newHeight = (int) (initHeight * scale);
//判断窗⼝缩放后是否超出屏幕⼤⼩
if (newWidth < screenWidth && newHeight < screenHeight) {
layoutParams.width = newWidth;
layoutParams.height = newHeight;
layoutParams.x = initX;
layoutParams.y = initY;
/
/缩放后图⽚会失真重新载⼊图⽚
Glide.with(MediaFloatService.this)
.(currentIndex))
.into(floatView.ivFloatMediaShow);
//提交更新布局
windowManager.updateViewLayout(mView, layoutParams);
}
return true;
//Scale(detector);
}
}
完整代码
floatView.ivFloatMediaShow.setOnTouchListener(new View.OnTouchListener() {
private int dX;
private int dY;
private int sX;
private int sY;
private ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(MediaFloatService.this, new myScale());    @Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (Action()) {
case MotionEvent.ACTION_DOWN:
dX = (int) RawX();
dY = (int) RawY();
sX = (int) RawX();
sY = (int) RawY();
isP2Down = true; //双指按下
break;
case MotionEvent.ACTION_MOVE:
if (PointerCount() == 1) {
//单指拖动
int nX = (int) RawX();
int nY = (int) RawY();
int cW = nX - dX;
int cH = nY - dY;
dX = nX;
dY = nY;
layoutParams.x = layoutParams.x + cW;
layoutParams.y = layoutParams.y + cH;
windowManager.updateViewLayout(mView, layoutParams);
} else if (PointerCount() == 2) {
//双指缩放
}
break;
case MotionEvent.ACTION_UP:
isP2Down = false; //双指抬起
//如果抬起时的位置和按下时的位置⼤致相同视作单击事件
int nX2 = (int) RawX();
int nY2 = (int) RawY();
int cW2 = nX2 - sX;
int cH2 = nY2 - sY;
//间隔值可能为负值,所以要取绝对值进⾏⽐较
if (Math.abs(cW2) < 3 && Math.abs(cH2) < 3) view.performClick();
break;
default:
break;
}
return true;
}
});
实例2
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private View floatView;
if(Settings.canDrawOverlays(this)){
windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
} else {
}
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.format = PixelFormat.RGBA_8888; //背景透明效果
layoutParams.width = 512; //悬浮窗⼝长宽值,单位为 px ⽽⾮ dp
layoutParams.height = 450;
android简单教程layoutParams.x = 100;
layoutParams.y = 100;
floatView = LayoutInflater.from(this).inflate(_float_view, null);
floatView.setAlpha((float) 0.9);
windowManager.addView(floatView, layoutParams);
}
常见问题
起始位置设置⽆效
想要x,y⽣效,⼀定要指定Gravity为top和left,想要居中就设置为 Center
layoutParams.x = 100; //启动位置
layoutParams.y = 100;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。