闲来无事,搞一波悬浮球,此球:

无需权限

无需权限

无需权限

主要代码只有一个类,简简单单放进自己的工程

悬浮球可以用来干啥:

打开侧滑界面

打开一排小按钮

打开客服等等

功能:

显示红点(接收到信息等场景)

关闭红点(关闭消息提示)

自动贴边

显示球

隐藏球

自定义点击事件及回调

可以说你能想到自定义的都可以自定义,因为下面会给出代码,任君扩展

先看看效果如何,图片大小有限制,所以我录得比较急一些,效果不是很好。


xghs2-gmnja.gif

这个悬浮球,我自觉还是蛮棒的,以下给出主要代码:

MainActivity.class

public class MainActivity extends Activity {

protected Button toButton,noButton,exitButton,jumpButton;

protected Context mContext;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

initVariables();

initViews(savedInstanceState);

loadData();

//造个悬浮球,并显示出来

SyFloatView.getInstance(this).show();

//这里可以根据需求,添加点击事件处理

SyFloatView.getInstance(this).setListener(new SyFloatView.FloatingLayerListener() {

@Override

public void onClick() {

//点击悬浮球事件

Log.e("MainAc","这是 点击悬浮球 监听回调。");

}

@Override

public void onClose() {

//关闭悬浮球事件

Log.e("MainAc","这是 关闭悬浮球 监听回调。");

}

});

}

@Override

protected void onResume() {

super.onResume();

//显示悬浮球:进入APP、登陆成功等场景下

SyFloatView.getInstance(mContext).display();

}

@Override

protected void onPause() {

super.onPause();

//隐藏悬浮球:回到桌面等场景

SyFloatView.getInstance(mContext).hide();

}

//关闭APP

protected void exitApp(){

//干掉悬浮球:可以在关闭APP、注销账号等场景下使用

SyFloatView.getInstance(mContext).close();

System.exit(0);

finish();

}

@Override

public boolean onKeyDown(int keyCode, KeyEvent event) {

switch(keyCode){

case KeyEvent.KEYCODE_BACK:

AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setTitle("退出提示");

builder.setMessage("确认要退出APP?");

builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { //设置确定按钮

@Override

public void onClick(DialogInterface dialog, int which) {

exitApp();

}

});

builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { //设置取消按钮

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

});

builder.create().show();

break;

}

return true;

}

//初始化

protected void initVariables() {

mContext = (Activity)this;

}

//初始化视图

protected void initViews(Bundle savedInstanceState) {

setContentView(R.layout.activity_main);

toButton = (Button) findViewById(R.id.to_btn);

noButton = (Button) findViewById(R.id.no_btn);

exitButton = (Button) findViewById(R.id.exit_app_btn);

jumpButton = (Button) findViewById(R.id.jump_btn);

//来消息了,显示消息红点

toButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

SyFloatView.getInstance(mContext).redDotVisible();

}

});

//朕已阅,点击关掉消息红点

noButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

SyFloatView.getInstance(mContext).redDotViewGone();

}

});

//关闭APP

exitButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

exitApp();

}

});

//跳转下一个Activity

jumpButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

Intent i = new Intent(MainActivity.this,MainActivity2.class);

startActivity(i);

}

});

}

//数据处理

protected void loadData() {

}

}

悬浮球代码,任君自定义,仅仅这一个类

public class SyFloatView {

private static SyFloatView sFloatingLayer;

private static final String TAG = "SyFloatView";

public static boolean IS_SHOW_BALL = false;

private WindowManager mWindowManager;

private WindowManager.LayoutParams mLayoutParams,halfPop_mLayoutParams;

private Context mContext;

private final int TOUCH_TIME_THRESHOLD = 150;

private View mPopView;

private float stickRightWidth;

private AnimationTimerTask mAnimationTask;

private Timer mAnimationTimer;

private GetTokenRunnable mGetTokenRunnable;

private long mLastTouchDownTime;

private FloatingLayerListener mListener;

private int mWidth,mHeight;

private float mPrevX, xInScreen, xDownInScreen;

private float mPrevY, yInScreen, yDownInScreen;

private int mAnimationPeriodTime = 16;

private static ImageView barRed, ballImage;

private Handler mHandler = new Handler();

private long lastClickTime;

public static SyFloatView getInstance(Context context) {

if (null == sFloatingLayer) {

synchronized (SyFloatView.class) {

if (null == sFloatingLayer) {

sFloatingLayer = new SyFloatView(context);

}

}

}

return sFloatingLayer;

}

private SyFloatView(Context context) {

this.mContext = context;

initView();

initWindowManager();

initLayoutParams();

initDrag();

}

private void initView() {

LayoutInflater layoutInflater = (LayoutInflater) ((Activity)mContext).getSystemService(Context.LAYOUT_INFLATER_SERVICE);

mPopView = layoutInflater.inflate(R.layout.sy_floating_view, null);

//这里可以自定义图片,消息红点

barRed = (ImageView) mPopView.findViewById(R.id.barRed);

ballImage = (ImageView) mPopView.findViewById(R.id.pop);

}

private void initWindowManager() {

mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

}

private void initLayoutParams() {

mWidth = mContext.getResources().getDisplayMetrics().widthPixels;

mHeight = mContext.getResources().getDisplayMetrics().heightPixels;

mLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,

ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,

WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,

PixelFormat.TRANSLUCENT);

mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;

//初始显示的位置

mLayoutParams.x = 0;

mLayoutParams.y = mContext.getResources().getDisplayMetrics().heightPixels / 3 * 2;

}

private void initDrag() {

mPopView.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View view, MotionEvent motionEvent) {

switch (motionEvent.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

mLastTouchDownTime = System.currentTimeMillis();

handler.removeMessages(1);

xInScreen = mPrevX = motionEvent.getRawX();

yInScreen = mPrevY = motionEvent.getRawY();

break;

case MotionEvent.ACTION_MOVE:

float deltaX = motionEvent.getRawX() - mPrevX;

float deltaY = motionEvent.getRawY() - mPrevY;

mLayoutParams.x += deltaX;

mLayoutParams.y += deltaY;

xDownInScreen = mPrevX = motionEvent.getRawX();

yDownInScreen = mPrevY = motionEvent.getRawY();

if (mLayoutParams.x < 0) mLayoutParams.x = 0;

if (mLayoutParams.x > mWidth - mPopView.getWidth())

mLayoutParams.x = mWidth - mPopView.getWidth();

if (mLayoutParams.y < 0) mLayoutParams.y = 0;

if (mLayoutParams.y > mHeight - mPopView.getHeight() * 2)

mLayoutParams.y = mHeight - mPopView.getHeight() * 2;

try {

mWindowManager.updateViewLayout(mPopView, mLayoutParams);

} catch (Exception e) {

Log.d(TAG, e.toString());

}

break;

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_UP:

if (isOnClickEvent()) {

//添加点击事件

if (isFastDoubleClick()) {

//防止连续点击,如果连续点击这里什么也不做

} else {

Toast.makeText(mContext, "你点击了悬浮球", Toast.LENGTH_SHORT).show();

//点击就让消息红点消失

redDotViewGone();

};

if (mListener != null) {

mListener.onClick();

}

}

if (mLayoutParams.x == stickRightWidth) {

mLayoutParams.x = mLayoutParams.x - mPopView.getWidth() / 2;

mWindowManager.updateViewLayout(mPopView, mLayoutParams);

sendMsgToHidePop();

return false;

}

mAnimationTimer = new Timer();

mAnimationTask = new AnimationTimerTask();

mAnimationTimer.schedule(mAnimationTask, 0, mAnimationPeriodTime);

sendMsgToHidePop();

break;

}

return false;

}

});

}

//防止连续点击

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;

}

/**

* 创建悬浮球并显示出来

*/

public void show() {

if (!IS_SHOW_BALL) {

mGetTokenRunnable = new GetTokenRunnable(((Activity)mContext));

mHandler.postDelayed(mGetTokenRunnable, 500);

IS_SHOW_BALL = true;

}

}

/**

* 杀掉悬浮球

*/

public void close() {

try {

if (IS_SHOW_BALL) {

mWindowManager.removeViewImmediate(mPopView);

if (null != mListener) mListener.onClose();

IS_SHOW_BALL = false;

}

} catch (Exception e) {

Log.d(TAG, e.toString());

}

}

/**

* 隐藏悬浮球

*/

public void hide() {

if (mPopView != null) mPopView.setVisibility(View.INVISIBLE);

}

/**

* 显示悬浮球

*/

public void display() {

if (mPopView != null) mPopView.setVisibility(View.VISIBLE);

}

/**

* 显示悬浮球的红点

*/

public void redDotVisible() {

if (barRed != null) barRed.setVisibility(View.VISIBLE);

}

/**

* 隐藏悬浮球的红点

*/

public void redDotViewGone() {

if (barRed != null) barRed.setVisibility(View.GONE);

}

public SyFloatView setListener(FloatingLayerListener listener) {

if (null != sFloatingLayer) this.mListener = listener;

return sFloatingLayer;

}

/**

* 监听接口

*/

public interface FloatingLayerListener {

void onClick();

void onClose();

}

/**

* handler处理:隐藏半球

*/

private Handler handler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

switch (msg.what) {

case 1:

hidePop();

break;

default:

break;

}

}

};

private static boolean isNearLeft = true;

class AnimationTimerTask extends TimerTask {

int mStepX;

int mDestX;

public AnimationTimerTask() {

if (mLayoutParams.x > mWidth / 2) {

isNearLeft = false;

mDestX = mWidth - mPopView.getWidth();

mStepX = (mWidth - mLayoutParams.x) / 10;

} else {

isNearLeft = true;

mDestX = 0;

mStepX = -((mLayoutParams.x) / 10);

}

}

@Override

public void run() {

if (Math.abs(mDestX - mLayoutParams.x) <= Math.abs(mStepX)) {

mLayoutParams.x = mDestX;

} else {

mLayoutParams.x += mStepX;

}

try {

mHandler.post(new Runnable() {

@Override

public void run() {

mWindowManager.updateViewLayout(mPopView, mLayoutParams);

}

});

} catch (Exception e) {

Log.d(TAG, e.toString());

}

if (mLayoutParams.x == mDestX) {

mAnimationTask.cancel();

mAnimationTimer.cancel();

}

}

}

public void sendMsgToHidePop() {

Message msg = new Message();

msg.what = 1;

handler.sendMessageDelayed(msg, 2500);

}

/**

* 隐藏悬浮球一半,贴在边缘

*/

private void hidePop() {

halfPop_mLayoutParams = mLayoutParams;

if (isNearLeft) {

halfPop_mLayoutParams.x = -(mPopView.getWidth() / 2);

} else {

halfPop_mLayoutParams.x = mLayoutParams.x + (mPopView.getWidth() / 2);

stickRightWidth = halfPop_mLayoutParams.x;

}

try {

mWindowManager.updateViewLayout(mPopView, halfPop_mLayoutParams);

} catch (Exception e) {

Log.d(TAG, "hidePop E :" + e.toString());

}

}

class GetTokenRunnable implements Runnable {

int count = 0;

private Activity mActivity;

public GetTokenRunnable(Activity activity) {

this.mActivity = activity;

}

@Override

public void run() {

if (null == mActivity) return;

IBinder token = null;

try {

token = mActivity.getWindow().getDecorView().getWindowToken();

} catch (Exception e) {

}

if (null != token) {

try {

mLayoutParams.token = token;

if (mWindowManager != null && mPopView != null && mLayoutParams != null)

mWindowManager.addView(mPopView, mLayoutParams);

mActivity = null;

return;

} catch (Exception e) {

}

}

count++;

mLayoutParams.token = null;

if (count < 10 && null != mLayoutParams) {

mHandler.postDelayed(mGetTokenRunnable, 500);

}

}

}

}

这个悬浮球,是别人送我的一个球,我自己修修改改缝缝补补就发来了,大家鼓掌👏。

有个小问题,华为现在手势直接左右侧滑屏可以退出APP,这个退出又不似真的杀死APP。再打开APP悬浮球就出不来了,也不知道哪里导致的,莫名其妙,我目前是把返回按钮那里直接处理了一下,弹出个询问是否退出。有人如果发现原因的话,麻烦留言教我一哈。

最后附上demo源码地址,方便你把工程放进你的项目,能帮我再优化一下那就更加万分感谢了。

欢迎各位点赞、评论、转发!