写文章的前言,我还不知道以什么开始所以省略吧,你知道什么是悬浮窗就好了。
悬浮窗主要是由WindowManager管理实现,我们平常的需求可以在app内部实现弹出窗,也可以通过service里面弹出悬浮窗,这样子就可以在任意位置拖动处理悬浮窗。
我们主要注意的是,在Android6.0以后需要权限(用户主动赋予),Android8.0修改了权限API这两点。好了,我知道字太多你也不想看,我也写不出。直接看代码。。。。
WindowManager mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
上面是获取windowManager对象,我们是通过这个来添加你想展示的布局,他提供以下几个方法
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
应该看方法名都知道他们分别是用来干嘛的,我们添加view需要第二个参数ViewGroup.LayoutParams params来定义样式了类型,在Android不同版本,悬浮窗的类型也不同,这个需要特别注意,要不然会报错崩溃。
wmParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
整个悬浮框的代码:
private static WindowManager mWindowManager;
private static WindowManager.LayoutParams wmParams;
public void showFloatView(Context context,int layoutId){
mContext = context;
if(isShow){
return;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//悬浮框居中
wmParams.gravity = Gravity.CENTER;
wmParams.format = PixelFormat.RGBA_8888;
//悬浮窗的坐标,以左上角为中心,固定初始x,y值
wmParams.x = context.getResources().getDisplayMetrics().widthPixels;
wmParams.y = 0;
//悬浮窗宽高,可以固定大小
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
//加载布局
mView = LayoutInflater.from(context).inflate(layoutId, null);
//添加布局到manager
mWindowManager.addView(mView, wmParams);
isShow = true;
}
接下来就是使用,因为我们是使用在Service中,所以需要在AndroidManifest中添加允许弹窗的权限,不能忘了
在service中调用:
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {//注意需要判断是否允许了权限
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
}else {
showFloatView();//有权限,去弹出悬浮窗这里
}
}
上面是需要判断canDrawOverlays,如下图
只有赋予了权限才可以弹出悬浮窗。,给与权限后,我们就可以看到自己的悬浮窗了
这样只是完成了一半,现在我们要让他可以移动,可以长按点击,就需要OnTouchListener这个了
mView.setOnTouchListener(new View.OnTouchListener() {
float downX = 0;
float downY = 0;
int oddOffsetX = 0;
int oddOffsetY = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
oddOffsetX = wmParams.x;
oddOffsetY = wmParams.y;
LogUtil.w("点击按下悬浮框==downX"+downX+";downY="+downY+";oddOffsetX="+oddOffsetX+";oddOffsetY="+oddOffsetY);
//实现长按悬浮窗点击事件
handler.postDelayed(runnable, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
if(!isMove){//让长摁悬浮窗时不导致他移动
break;
}
float moveX = event.getX();
float moveY = event.getY();
//防止拖动太快
wmParams.x += (moveX - downX)/3;
wmParams.y += (moveY - downY)/3;
LogUtil.w("点击“移动”悬浮框==moveX"+moveX+";moveY="+moveY+";wmParams.x="+wmParams.x+";wmParams.y="+wmParams.y);
LogUtil.d("移动的距离==x:"+Math.abs(moveX - downX)+";y=="+ Math.abs(moveY - downY));
int scaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if (Math.abs(moveX - downX) > scaledTouchSlop || Math.abs(moveY - downY) > scaledTouchSlop) { //移动超过了阈值,表示移动了
isMove = true; //移除runnable
handler.removeCallbacks(runnable);
if(mView != null){
//更新视图
mWindowManager.updateViewLayout(mView,wmParams);
}
}
break;
case MotionEvent.ACTION_UP:
int newOffsetX = wmParams.x;
int newOffsetY = wmParams.y;
handler.removeCallbacks(runnable);
LogUtil.w("点击“抬起”悬浮框==newOffsetX="+newOffsetX+";newOffsetY="+newOffsetY+";oddOffsetX="+oddOffsetX+";oddOffsetY="+oddOffsetY);
LogUtil.d("移动的距离==x:"+Math.abs(newOffsetX - oddOffsetX)+";y=="+Math.abs(newOffsetY - oddOffsetY));
if(Math.abs(newOffsetX - oddOffsetX) <=20 || Math.abs(newOffsetY - oddOffsetY) <=20){
isMove = true;
//添加接口回调
if(mOnFloatClickListener != null){
if(mView != null){
mWindowManager.updateViewLayout(mView,wmParams);
}
mOnFloatClickListener.onCancelClick(mView);
}
}
break;
case MotionEvent.ACTION_CANCEL:
LogUtil.w("取消");
break;
}
return true;
}
});
通过上面的代码,我们就可以实现拖动和长按点击事件了,长按能实现,点击事件很简单,你只需要将取消的事件变成点击事件就行。下图为长摁改变布局
好了,我现在要贴出所有代码来了,
package com.android.ui;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.ImageView;
import com.android.util.LogUtil;
public class ScannerFloatView {
private static Context mContext;
private static WindowManager mWindowManager;
private static WindowManager.LayoutParams wmParams;
private static View mView;
private static boolean isShow = false;//悬浮框是否已经显示
private static OnClickListener mListener;//view的点击回调listener
private static OnFloatClickListener mOnFloatClickListener;
private ImageView ivLinght;
private ImageView ivLinghtClose;
public void setOnClickListener(OnClickListener listener){
mListener = listener;
}
public interface OnClickListener{
void onClick(View view);
}
public interface OnFloatClickListener{
void onCancelClick(View view);
void onLongClick();
}
public void setOnFloatClickListener(OnFloatClickListener onFloatClickListener){
mOnFloatClickListener = onFloatClickListener;
}
private static ScannerFloatView floatView = null;
private ScannerFloatView(){
}
public static synchronized ScannerFloatView getInstance(){
if(floatView == null){
floatView = new ScannerFloatView();
}
return floatView;
}
/**
* 是否移动
*/
private boolean isMove = true;
/**
* 显示悬浮框
*/
public void showFloatView(Context context,int layoutId){
mContext = context;
if(isShow){
return;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//哭了。。。。我靠8.0又改了
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
wmParams.gravity = Gravity.CENTER;
wmParams.format = PixelFormat.RGBA_8888;
wmParams.x = context.getResources().getDisplayMetrics().widthPixels;
wmParams.y = 0;
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mView = LayoutInflater.from(context).inflate(layoutId, null);
ivLinght = mView.findViewById(R.id.iv_lll);
mWindowManager.addView(mView, wmParams);
mView.setOnTouchListener(new View.OnTouchListener() {
float downX = 0;
float downY = 0;
int oddOffsetX = 0;
int oddOffsetY = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
oddOffsetX = wmParams.x;
oddOffsetY = wmParams.y;
LogUtil.w("点击按下悬浮框==downX"+downX+";downY="+downY+";oddOffsetX="+oddOffsetX+";oddOffsetY="+oddOffsetY);
handler.postDelayed(runnable, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
if(!isMove){
break;
}
float moveX = event.getX();
float moveY = event.getY();
//不除以3,拖动的view抖动的有点厉害
wmParams.x += (moveX - downX)/3;
wmParams.y += (moveY - downY)/3;
LogUtil.w("点击“移动”悬浮框==moveX"+moveX+";moveY="+moveY+";wmParams.x="+wmParams.x+";wmParams.y="+wmParams.y);
LogUtil.d("移动的距离==x:"+Math.abs(moveX - downX)+";y=="+ Math.abs(moveY - downY));
int scaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if (Math.abs(moveX - downX) > scaledTouchSlop || Math.abs(moveY - downY) > scaledTouchSlop) { //移动超过了阈值,表示移动了
isMove = true; //移除runnable
handler.removeCallbacks(runnable);
if(mView != null){
mWindowManager.updateViewLayout(mView,wmParams);
}
}
break;
case MotionEvent.ACTION_UP:
int newOffsetX = wmParams.x;
int newOffsetY = wmParams.y;
handler.removeCallbacks(runnable);
LogUtil.w("点击“抬起”悬浮框==newOffsetX="+newOffsetX+";newOffsetY="+newOffsetY+";oddOffsetX="+oddOffsetX+";oddOffsetY="+oddOffsetY);
LogUtil.d("移动的距离==x:"+Math.abs(newOffsetX - oddOffsetX)+";y=="+Math.abs(newOffsetY - oddOffsetY));
if(Math.abs(newOffsetX - oddOffsetX) <=20 || Math.abs(newOffsetY - oddOffsetY) <=20){
isMove = true;
if(mOnFloatClickListener != null){
if(mView != null){
mWindowManager.updateViewLayout(mView,wmParams);
}
mOnFloatClickListener.onCancelClick(mView);
}
}
break;
case MotionEvent.ACTION_CANCEL:
LogUtil.w("取消");
break;
}
return true;
}
});
isShow = true;
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
LogUtil.i("长按了vvvvv");
isMove = false;
if(mOnFloatClickListener != null){
mWindowManager.updateViewLayout(mView,wmParams);
mOnFloatClickListener.onLongClick();
}
}
};
private Handler handler = new Handler();
/**
* 隐藏悬浮窗
*/
public void hideFloatView(){
if(mWindowManager != null && isShow){
mWindowManager.removeView(mView);
isShow = false;
}
}
}
调用
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
}else {
showFloatView();
}
}
展示悬浮框,调用
/**
* 显示悬浮窗
*/
private void showFloatView() {
floatView = ScannerFloatView.getInstance();
floatView.showFloatView(getApplication(), R.layout.layout_float_click);
floatView.setOnFloatClickListener(new ScannerFloatView.OnFloatClickListener() {
@Override
public void onCancelClick(View view) {
if(SharePreConfig.getScanmode() == 0)
keySwitchToTigger(TRIGGER_CANCEL_VALUE);
}
@Override
public void onLongClick() {
keySwitchToTigger(TRIGGER_CANCEL_VALUE);
keySwitchToTigger(TRIGGER_VALUE);
}
});
}
/**
* 隐藏悬浮窗
*/
public void hideFloatView(){
if(floatView != null){
floatView.hideFloatView();
}
}