android悬浮窗组件,Android 悬浮窗,悬浮view功能实现

写在前面:本文仅个人开发时遇到的问题及个人解决办法的记录。

国内各个手机厂商对ROM魔改的比较严重,还没有做兼容性测试,所以碰到沙雕的机子的时候,请再去寻找适配方法

最近项目开发中,需要实现一个悬浮窗,说一下实现方式,做一下记录。

首先,简单的藐视就是:实现悬浮窗是用的WindowManager。利用context.getSystemService(Context.WINDOW_SERVICE)获取到WindowManger对象,调用里面的windowManager.addView(floatView, layoutParams)方法。floatView就是要展示的悬浮窗的View layoutParams是一些参数设置。

下面介绍一下实现步骤(懒得看的可以下滑到最下面看代码):

申请权限,这是必须的一步。

在你的清单文件中添加如下权限代码

然后在代码里面使用下面的方法判断是否有悬浮窗权限:

Settings.canDrawOverlays(context)

如果没有权限,跳转到权限开启页面,打开悬浮窗权限。确切的说是跳转到开启 允许显示在其他应用上层的权限

startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 10086);

一切准备工作完成后开始我们的正式任务啊!!!!!!!!!!

第一步,获取到WindowManager对象;

(WindowManager) context.getSystemService(Context.WINDOW_SERVICE)

第二步,创建一个WindowManager.LayoutParams对象,用于设置一些悬浮view的参数

// 设置LayoutParam

layoutParams =new WindowManager.LayoutParams();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}else {

layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;

}

//悬浮窗弹出的位置

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

//注意:这一个flags的设置,之前搜索很多实现都没有设置这个,出现的情况就是在悬浮的view出现后  点击窗口其它地方没有反应,是因为不设置这个参数,悬浮窗弹出来后就占据整个窗口的焦点。

layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

layoutParams.format = PixelFormat.RGBA_8888;

layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.x =0;

layoutParams.y =0;

第三步 获取到需要悬浮显示的view对象

LayoutInflater layoutInflater = LayoutInflater.from(context);

View floatView = layoutInflater.inflate(R.layout.floating_view, null);

第四步,将悬浮view和layoutParams调用windowmanager的方法addView显示出来

// 将悬浮窗控件添加到WindowManager

windowManager.addView(floatView, layoutParams);

如果你需要对悬浮窗里不同view设置一些点击事件

我们在上面第三步里面获取到了悬浮窗的View对象,可以使用view.findviewById方法根据id拿到各个view,针对不同的view设置不同的事件。

对悬浮窗添加拖动事件

同样上面第三步我们获取到的view对象,设置触摸事件

//设置触摸事件

floatView.setOnTouchListener(new FloatingOnTouchListener());

//因为我的悬浮窗需求比较简单,所以没有那么多复杂的操作。只是拖动后,让悬浮view靠边停着。

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

private int y;

//标记是否执行move事件 如果执行了move事件  在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边

private boolean isScroll;

//标记悬浮窗口是否移动了  防止设置点击事件的时候 窗口移动松手后触发点击事件

private boolean isMoved;

//事件开始时和结束的时候  X和Y坐标位置

private int startX;

private int startY;

@Override

public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

y = (int) event.getRawY();

isMoved =false;

isScroll =false;

startX = (int) event.getRawX();

startY = (int) event.getRawY();

break;

case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

int nowY = (int) event.getRawY();

int movedX = nowX -x;

int movedY = nowY -y;

x = nowX;

y = nowY;

layoutParams.x =layoutParams.x + movedX;

layoutParams.y =layoutParams.y + movedY;

// 更新悬浮窗控件布局

windowManager.updateViewLayout(view, layoutParams);

isScroll =true;

break;

case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

int stopY = (int) event.getRawY();

if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

}

if (isScroll){

autoView(view);

}

break;

}

return isMoved;

}

//悬浮窗view自动停靠在屏幕左边或者右边

private void autoView(View view) {

// 得到view在屏幕中的位置

int[] location =new int[2];

view.getLocationOnScreen(location);

if (location[0]

layoutParams.x =0;

}else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

}

windowManager.updateViewLayout(view, layoutParams);

}

}

最后,放出来一个简单处理的类,有需求的可以根据需求自己修改

代码:

public class FloatingWindowUtils {

private Contextcontext;

private int screenWidth;

private WindowManager.LayoutParamslayoutParams;

private WindowManagerwindowManager;

private ViewfloatView;

private FloatingWindowUtils() {

}

private static class InstanceHolder {

@SuppressLint("StaticFieldLeak")

private static final FloatingWindowUtilssInstance =new FloatingWindowUtils();

private InstanceHolder() {

}

}

public static FloatingWindowUtilsgetInstance() {

return FloatingWindowUtils.InstanceHolder.sInstance;

}

public void init(Context context) {

this.context = context;

if (windowManager !=null)return;

// 获取WindowManager服务

windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

//获取屏宽

screenWidth = DensityUtils.getScreenSize(false).x;

}

/**

* 展示悬浮窗

* @param layoutId 悬浮窗布局文件id

*/

@SuppressLint("RtlHardcoded")

public void showFloatingWindow(@LayoutRes int layoutId){

// 新建悬浮窗控件

LayoutInflater layoutInflater = LayoutInflater.from(context);

//        View floatView = layoutInflater.inflate(R.layout.floating_view, null);

View floatView = layoutInflater.inflate(layoutId, null);

if (floatView ==null){

throw new NullPointerException("悬浮窗view为null 检查布局文件是否可用");

}

showFloatingWindow(floatView);

}

/**

* 展示悬浮窗

* @param floatView 悬浮窗view

*/

@SuppressLint("RtlHardcoded")

public void showFloatingWindow(@NonNull View floatView){

if (this.floatView !=null)return;//有悬浮窗在显示 不再显示新的悬浮窗

// 新建悬浮窗控件

if (floatView ==null){

throw new NullPointerException("悬浮窗view为null 确认view不为null");

}

this.floatView = floatView;

//设置触摸事件

floatView.setOnTouchListener(new FloatingOnTouchListener());

//悬浮窗设置点击事件

floatView.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

Toast.makeText(context, "点击了悬浮窗", Toast.LENGTH_SHORT).show();

}

});

// 设置LayoutParam

layoutParams =new WindowManager.LayoutParams();

if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

//设置flags 不然悬浮窗出来后整个屏幕都无法获取焦点,

layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

layoutParams.format = PixelFormat.RGBA_8888;

layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.x =0;

layoutParams.y =0;

// 将悬浮窗控件添加到WindowManager

windowManager.addView(floatView, layoutParams);

}

/**

* 隐藏悬浮窗

*/

public void hideFloatWindow(){

if (floatView !=null){

windowManager.removeViewImmediate(floatView);

floatView =null;

}

}

public void unInit() {

hideFloatWindow();

this.context =null;

// 获取WindowManager服务

windowManager =null;

}

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

private int y;

//标记是否执行move事件 如果执行了move事件  在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边

private boolean isScroll;

//标记悬浮窗口是否移动了  防止设置点击事件的时候 窗口移动松手后触发点击事件

private boolean isMoved;

//事件开始时和结束的时候  X和Y坐标位置

private int startX;

private int startY;

@Override

public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

y = (int) event.getRawY();

isMoved =false;

isScroll =false;

startX = (int) event.getRawX();

startY = (int) event.getRawY();

break;

case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

int nowY = (int) event.getRawY();

int movedX = nowX -x;

int movedY = nowY -y;

x = nowX;

y = nowY;

layoutParams.x =layoutParams.x + movedX;

layoutParams.y =layoutParams.y + movedY;

// 更新悬浮窗控件布局

windowManager.updateViewLayout(view, layoutParams);

isScroll =true;

break;

case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

int stopY = (int) event.getRawY();

if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

}

if (isScroll){

autoView(view);

}

break;

}

return isMoved;

}

//悬浮窗view自动停靠在屏幕左边或者右边

private void autoView(View view) {

// 得到view在屏幕中的位置

int[] location =new int[2];

view.getLocationOnScreen(location);

if (location[0]

layoutParams.x =0;

}else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

}

windowManager.updateViewLayout(view, layoutParams);

}

}

public ViewgetFloatView(){

return floatView;

}

}

最后给大家放一个介绍比较全面的帖子。

https://www.jianshu.com/p/3246f7289704

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值