前言:
最近闲来无事,突然想起搞一下悬浮球,之前的项目的悬浮球一直都需要授权,android6.0以后需要手动授权,悬浮球使用时就非常不便,这里为大家带来一种无需权限的悬浮球实现方式。
- 无需权限!
- 无需权限!
功能:
- 自动贴边
- 显示红点
- 隐藏红点
- 自由移动
- 显示悬浮球
- 隐藏悬浮球
- 销毁悬浮球
- 接入简单,可进行自定义拓展
附上demo地址
效果如下:
[图片上传失败...(image-a6f66e-1612423455825)]
以下是主要的代码部分:
MainActivity.class
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.showbtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RoundView.getInstance().showRoundView(MainActivity.this);
}
});
findViewById(R.id.hidebtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RoundView.getInstance().hideRoundView(MainActivity.this);
}
});
findViewById(R.id.closebtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RoundView.getInstance().closeRoundView(MainActivity.this);
}
});
findViewById(R.id.showMsgbtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RoundView.getInstance().showRoundMsg();
}
});
findViewById(R.id.hideMsgbtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RoundView.getInstance().hideRoundMsg();
}
});
findViewById(R.id.exitbtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
android.os.Process.killProcess(android.os.Process.myPid());
}
});
}
}
RoundView.class
/**
* 悬浮窗管理器
*/
public class RoundView {
private static RoundView sFloatingLayer;
/**
* 用于控制在屏幕上添加或移除悬浮窗
*/
private static WindowManager mWindowManager;
public static boolean isNearLeft = true; //判断悬浮球是否在左边
private static boolean isShow = false; //判断悬浮球是否已经显示
private int mWidth,mHeight; //屏幕的宽高
public static final int WIN_NONE = 0;// 不展示
public static final int WIN_SMALL = 1;// 小浮标
public static final int WIN_BIG = 2;// 展开
public static final int WIN_HIDE = 3;// 靠边隐藏 无操作隐藏
public static int winStatus;
public static boolean isMsg=false; //红点消息提示是否显示
/**
* 小悬浮窗view的实例
*/
private static RoundWindowSmallView smallWindow;
/**
* 隐藏悬浮窗view的实例
*/
private static RoundWindowHideView hideWindow;
/**
* 大悬浮窗view的实例
*/
private static RoundWindowBigView bigWindow;
/**
* 大小悬浮窗view的参数
*/
private static WindowManager.LayoutParams mLayoutParams;
public static RoundView getInstance() {
if (null == sFloatingLayer) {
synchronized (RoundView.class) {
if (null == sFloatingLayer) {
sFloatingLayer = new RoundView();
}
}
}
return sFloatingLayer;
}
/**
* 显示悬浮窗
* @param context
*/
public void showRoundView(Context context) {
if(mWindowManager==null){
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
//每一次显示浮窗前都重新获取一次宽高,避免横竖屏切换后宽高变化
getWidthAndHeight(context);
if(!isShow){
//处于非显示状态,可以显示
isShow=true;
if(winStatus==WIN_NONE){
//处于未创建状态,请创建
showSmallwin(context);
}else {
//已创建了,直接显示
switch (winStatus){
case WIN_SMALL:
if(smallWindow!=null)
smallWindow.setVisibilityState(View.VISIBLE);
break;
case WIN_BIG:
if(bigWindow!=null)
bigWindow.setVisibilityState(View.VISIBLE);
break;
case WIN_HIDE:
if(hideWindow!=null)
hideWindow.setVisibilityState(View.VISIBLE);
break;
}
}
}
}
/**
* 隐藏悬浮窗
* @param context
*/
public void hideRoundView(Context context) {
if(isShow){
//处于显示状态,可以隐藏
isShow=false;
switch (winStatus){
case WIN_SMALL:
if(smallWindow!=null)
smallWindow.setVisibilityState(View.GONE);
break;
case WIN_BIG:
if(bigWindow!=null)
bigWindow.setVisibilityState(View.GONE);
break;
case WIN_HIDE:
if(hideWindow!=null)
hideWindow.setVisibilityState(View.GONE);
break;
}
}
}
/**
* 销毁悬浮窗
* @param context
*/
public void closeRoundView(Context context) {
isShow=false;
isMsg=false;
winStatus=WIN_NONE;
removeSmallWindow(context);
removeBigWindow(context);
removeHideWindow(context);
}
/**
* 显示红点提示
*/
public void showRoundMsg() {
if(!isMsg){
isMsg=true;
switch (winStatus){
case WIN_SMALL:
if(smallWindow!=null)
smallWindow.showRoundMsg();
break;
case WIN_BIG:
if(bigWindow!=null)
bigWindow.showRoundMsg();
break;
case WIN_HIDE:
if(hideWindow!=null)
hideWindow.showRoundMsg();
break;
}
}
}
/**
* 隐藏红点提示
*/
public void hideRoundMsg() {
if(isMsg){
isMsg=false;
switch (winStatus){
case WIN_SMALL:
if(smallWindow!=null)
smallWindow.hideRoundMsg();
break;
case WIN_BIG:
if(bigWindow!=null)
bigWindow.hideRoundMsg();
break;
case WIN_HIDE:
if(hideWindow!=null)
hideWindow.hideRoundMsg();
break;
}
}
}
/**
* 显示小悬浮窗
* @param context context
*/
public void showSmallwin(final Context context) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
createSmallWindow(context);
removeBigWindow(context);
removeHideWindow(context);
}
}, 500);
}
/************************************************ 创建窗口 ************************************************************/
/**
* 创建一个小悬浮窗。初始位置在屏幕的左上角位置
*
* @param context 必须为应用程序的Context
*/
public void createSmallWindow(Context context) {
//每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
getWidthAndHeight(context);
if (smallWindow == null) {
smallWindow = new RoundWindowSmallView(context);
if (mLayoutParams == null) {
mLayoutParams = new WindowManager.LayoutParams();
mLayoutParams.format = PixelFormat.RGBA_8888;// 解决带Alpha的32位png图片失真问题
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; //显示在左上角
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 在设置宽高
mLayoutParams.x = 0;
mLayoutParams.y = context.getResources().getDisplayMetrics().heightPixels / 3 * 2;
mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
}
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; //设置悬浮窗的层次
if (!isNearLeft) {// 当在屏幕右侧时 重新计算x坐标
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
smallWindow.measure(w, h);
if (Configuration.ORIENTATION_LANDSCAPE == context.getResources().getConfiguration().orientation) {// 横屏
mLayoutParams.x = mWidth - smallWindow.getMeasuredWidth();
} else {// 竖屏
mLayoutParams.x = mWidth - smallWindow.getMeasuredWidth();
}
}
smallWindow.setParams(mLayoutParams);
// 将悬浮球添加到窗体
if (smallWindow.getParent() == null) {
mWindowManager.addView(smallWindow, mLayoutParams);
}
winStatus=WIN_SMALL;
smallWindow.timehide();// 小悬浮窗3s后隐藏动画
}
/**
* 将小悬浮窗从屏幕上移除
*
* @param context 必须为应用程序的context
*/
public void removeSmallWindow(Context context) {
if (smallWindow != null) {
smallWindow.stopDelayed();
if (context != null) {
if (mWindowManager != null) {
try {
mWindowManager.removeView(smallWindow);
} catch (Exception e) {
}
}
smallWindow = null;
} else {
smallWindow = null;
}
}
}
/**
* 创建一个隐藏悬浮窗。
*
* @param context 必须为应用程序的Context
*/
public void createHideWindow(Context context) {
//每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
getWidthAndHeight(context);
if (hideWindow == null) {
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
smallWindow.measure(w, h);// 测量
int width = smallWindow.getMeasuredWidth();// 获得视图实际宽度(测量宽度)
if (isNearLeft) {
hideWindow = new RoundWindowHideView(context);
} else {
hideWindow = new RoundWindowHideView(context);
mLayoutParams.x = mLayoutParams.x + width / 2;
}
}
mWindowManager.addView(hideWindow, mLayoutParams);
winStatus=WIN_HIDE;
}
/**
* 将隐藏悬浮窗从屏幕上移除
*
* @param context 必须为应用程序的context
*/
public void removeHideWindow(Context context) {
if (hideWindow != null) {
if (context != null) {
if (mWindowManager != null) {
mWindowManager.removeView(hideWindow);
}
}
hideWindow = null;
}
}
/**
* 创建一个大悬浮窗。
*
* @param context 必须为应用程序的Context
*/
public void createBigWindow(Context context) {
//每一次创建前都要重新获取宽高,不然横竖屏切换时会出问题
getWidthAndHeight(context);
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
if (bigWindow == null) {
if (smallWindow != null) {
smallWindow.measure(w, h);
} else if (hideWindow != null) {
hideWindow.measure(w, h);
}
bigWindow = new RoundWindowBigView(context);
}
bigWindow.measure(w,h);
if (mLayoutParams.x>(mWidth-bigWindow.getMeasuredWidth())){
// 在右边拖动的时候需要减去虚拟按钮
mLayoutParams.x = mWidth-bigWindow.getMeasuredWidth();
}
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL+1; //设置悬浮窗的层次 数据越大则越下面
mWindowManager.addView(bigWindow, mLayoutParams);
winStatus=WIN_BIG;
}
/**
* 将大悬浮窗从屏幕上移除
*
* @param context 必须为应用程序的context
*/
public void removeBigWindow(Context context) {
if (bigWindow != null) {
if (context != null) {
if (mWindowManager != null) {
mWindowManager.removeView(bigWindow);
}
}
bigWindow = null;
}
}
/**
* 获取全屏状态下的宽高
* @param context
*/
public void getWidthAndHeight(Context context){
mWidth=context.getResources().getDisplayMetrics().widthPixels;
mHeight = context.getResources().getDisplayMetrics().heightPixels;
//有的手机是有虚拟导航键的,当横屏且全屏时,悬浮球无法靠到最右边,所以要用包含虚拟导航键的屏幕宽度
Point point = new Point();
if (mWindowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mWindowManager.getDefaultDisplay().getRealSize(point);
mWidth=point.x;
//mHeight =point.y;
}
}
}
}
RoundWindowBigView.class
/**
* 悬浮窗展开状态
*/
public class RoundWindowBigView extends LinearLayout {
private Context context;
private ImageView iv_content;
/**
* 红点消息提示
*/
private View msg;
public RoundWindowBigView(Context context) {
super(context);
this.context = context;
if (RoundView.isNearLeft) {
LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "pop_left"), this);
} else {
LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "pop_right"), this);
}
iv_content = (ImageView) findViewById( FileUtil.getResIdFromFileName(context, "id", "iv_content"));
msg=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg"));
if(RoundView.isMsg){
msg.setVisibility(VISIBLE);
}else {
msg.setVisibility(GONE);
}
setupViews();
}
private void setupViews() {
iv_content.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
new Handler().post(new Runnable() {
@Override
public void run() {
// 只能先创建在移除,不然有问题
RoundView.getInstance().createSmallWindow(context);
RoundView.getInstance().removeBigWindow(context);
}
});
}
});
}
public void setVisibilityState(int state){
this.setVisibility(state);
}
/**
* 红点消息显示
*/
public void showRoundMsg(){
msg.setVisibility(VISIBLE);
}
/**
* 红点消息隐藏
*/
public void hideRoundMsg(){
msg.setVisibility(GONE);
}
}
RoundWindowHideView.class
/**
* 悬浮窗贴边状态
*/
public class RoundWindowHideView extends LinearLayout {
private ImageView hideview;
private Context context;
/**
* 红点消息提示
*/
private View msg;
public RoundWindowHideView(Context context) {
super(context);
this.context = context;
if (RoundView.isNearLeft) {
LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "layout_hide_float_left"), this);
} else {
LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "layout_hide_float_right"), this);
}
hideview = (ImageView) findViewById( FileUtil.getResIdFromFileName(context, "id", "hide_float_iv"));
msg=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg"));
if(RoundView.isMsg){
msg.setVisibility(VISIBLE);
}else {
msg.setVisibility(GONE);
}
setupViews();
}
private void setupViews() {
hideview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// 只能先创建在移除,不然有问题
RoundView.getInstance().createSmallWindow(context);
RoundView.getInstance().removeHideWindow(context);
}
});
}
public void setVisibilityState(int state){
this.setVisibility(state);
}
/**
* 红点消息显示
*/
public void showRoundMsg(){
msg.setVisibility(VISIBLE);
}
/**
* 红点消息隐藏
*/
public void hideRoundMsg(){
msg.setVisibility(GONE);
}
}
RoundWindowSmallView.class
/**
* 悬浮窗常规状态
*/
public class RoundWindowSmallView extends LinearLayout {
private static final String TAG = "RoundView";
private Context context;
private WindowManager windowManager;
private WindowManager.LayoutParams mParams;
private float mPrevX;
private float mPrevY;
private int mWidth,mHeight;
private Timer mAnimationTimer;
private AnimationTimerTask mAnimationTask;
private Handler mHandler = new Handler();
private int mAnimationPeriodTime = 16;
private long mLastTouchDownTime; //按下的时间
private final int TOUCH_TIME_THRESHOLD = 150; //少于150毫秒算点击事件
private long lastClickTime;
/**
* 小悬浮窗对象
*/
private View view;
/**
* 红点消息提示
*/
private View msgLeft,msgRight;
public RoundWindowSmallView(final Context context) {
super(context);
this.context=context;
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWidth=getContext().getResources().getDisplayMetrics().widthPixels;
mHeight = getContext().getResources().getDisplayMetrics().heightPixels;
Point point = new Point();
if (windowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
windowManager.getDefaultDisplay().getRealSize(point);
mWidth=point.x;
//mHeight =point.y;
}
}
// 获取布局文件
LayoutInflater.from(context).inflate(FileUtil.getResIdFromFileName(context, "layout", "round_view"), this);
view = findViewById(FileUtil.getResIdFromFileName(context, "id", "rl_content"));
msgLeft=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg_left"));
msgRight=findViewById(FileUtil.getResIdFromFileName(context, "id", "round_msg_right"));
if(RoundView.isMsg){
if(RoundView.isNearLeft){
msgLeft.setVisibility(VISIBLE);
}else {
msgRight.setVisibility(VISIBLE);
}
}else {
msgLeft.setVisibility(GONE);
msgRight.setVisibility(GONE);
}
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: //按下
mLastTouchDownTime = System.currentTimeMillis();
handler.removeMessages(1);
mPrevX = motionEvent.getRawX();
mPrevY = motionEvent.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float deltaX = motionEvent.getRawX() - mPrevX;
float deltaY = motionEvent.getRawY() - mPrevY;
mParams.x += deltaX;
mParams.y += deltaY;
mPrevX = motionEvent.getRawX();
mPrevY = motionEvent.getRawY();
//判断是否越界
if (mParams.x < 0) mParams.x = 0;
if (mParams.x > mWidth - view.getWidth())
mParams.x = mWidth - view.getWidth();
if (mParams.y < 0) mParams.y = 0;
if (mParams.y > mHeight - view.getHeight())
mParams.y = mHeight - view.getHeight();
try {
windowManager.updateViewLayout(view, mParams);
} catch (Exception e) {
Log.d(TAG, e.toString());
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: //松开
if (isOnClickEvent()) {
//添加点击事件
if (isFastDoubleClick()) {
//防止连续点击,如果连续点击这里什么也不做
} else {
//Toast.makeText(context, "你点击了悬浮球", Toast.LENGTH_SHORT).show();
new Handler().post(new Runnable() {
@Override
public void run() {
RoundView.getInstance().createBigWindow(context);
RoundView.getInstance().removeSmallWindow(context);
}
});
return false;
}
}
//贴边
mAnimationTimer = new Timer();
mAnimationTask = new AnimationTimerTask();
mAnimationTimer.schedule(mAnimationTask, 0,mAnimationPeriodTime);
timehide(); //3s钟后隐藏
break;
}
return false;
}
});
}
/**
* 将小悬浮窗的参数传入,用于更新小悬浮窗的位置
*
* @param params 小悬浮窗的参数
*/
public void setParams(WindowManager.LayoutParams params) {
mParams = params;
}
//防止连续点击
private boolean isFastDoubleClick() {
long time = System.currentTimeMillis();
if (time - lastClickTime < 500) { //点击间隔设置大于动画执行时间,避免重复点击造成多次执行动画
return true;
}
lastClickTime = time;
return false;
}
protected boolean isOnClickEvent() {
return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
}
class AnimationTimerTask extends TimerTask {
int mStepX;
int mDestX;
public AnimationTimerTask() {
if (mParams.x > mWidth / 2) {
RoundView.isNearLeft = false;
mDestX = mWidth - view.getWidth();
mStepX = (mWidth - mParams.x) / 10;
} else {
RoundView.isNearLeft = true;
mDestX = 0;
mStepX = -((mParams.x) / 10);
}
}
@Override
public void run() {
if (Math.abs(mDestX - mParams.x) <= Math.abs(mStepX)) {
mParams.x = mDestX;
} else {
mParams.x += mStepX;
}
try {
mHandler.post(new Runnable() {
@Override
public void run() {
updateViewPosition();
}
});
} catch (Exception e) {
Log.d(TAG, e.toString());
}
if (mParams.x == mDestX) {
mAnimationTask.cancel();
mAnimationTimer.cancel();
}
}
}
public void setVisibilityState(int state){
if(state==View.VISIBLE){
timehide();
}else if(state==View.GONE){
handler.removeMessages(1);
}
this.setVisibility(state);
}
public void updateViewPosition(){
windowManager.updateViewLayout(this, mParams);
}
/**
* 显示小浮标是否添加3秒隐藏
*/
public void timehide() {
Message msg = new Message();
msg.what = 1;
handler.sendMessageDelayed(msg, 3000);
}
/**
* 小浮标停止隐藏
*/
public void stopDelayed() {
if (handler != null) {
handler.removeMessages(1);
}
}
/**
* handler处理:隐藏半球
*/
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
hidePop();
break;
default:
break;
}
}
};
/**
* 退出动画
*
*/
public void hidePop(){
Animation mAnimation = null;
if (RoundView.isNearLeft) {
mAnimation = AnimationUtils.loadAnimation(context, FileUtil.getResIdFromFileName(context, "anim", "slide_out_left"));
} else {
mAnimation = AnimationUtils.loadAnimation(context, FileUtil.getResIdFromFileName(context, "anim", "slide_out_right"));
}
view.startAnimation(mAnimation);
mAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
//动画执行完毕后切换视图
// 只能先创建在移除,不然有问题
RoundView.getInstance().createHideWindow(context);
RoundView.getInstance().removeSmallWindow(context);
}
});
}
/**
* 红点消息显示
*/
public void showRoundMsg(){
if(RoundView.isNearLeft){
msgLeft.setVisibility(VISIBLE);
}else {
msgRight.setVisibility(VISIBLE);
}
}
/**
* 红点消息隐藏
*/
public void hideRoundMsg(){
msgLeft.setVisibility(GONE);
msgRight.setVisibility(GONE);
}
}