Android 悬浮窗悬浮球开发

枕上从妨一夜睡,灯前读尽十年诗。这篇文章主要讲述Android 悬浮窗悬浮球开发相关的知识,希望能为你提供帮助。
原文:Android 悬浮窗、悬浮球开发1、权限管理直接看我另外一篇博客吧,传送门:
https://my.oschina.net/u/1462828/blog/1933162
2、Base类BaseSuspend

import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import com.imxiaoyu.common.utils.entity.SizeEntity; public abstract class BaseSuspend { private Context context; private View view; private boolean isShowing = false; /** * UI */ private WindowManager.LayoutParams wmParams; //悬浮窗的布局/** * 变量 */ private WindowManager mWindowManager; //创建浮动窗口设置布局参数的对象/** * 接口 */ private OnSuspendDismissListener onSuspendDismissListener; public BaseSuspend(Context context) { this.context = context; view = LayoutInflater.from(context).inflate(getLayoutId(), null); init(); initView(); onCreateSuspension(); }public void init() { if (mWindowManager == null) { mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); } wmParams = getParams(); //设置好悬浮窗的参数 // 悬浮窗默认显示以左上角为起始坐标 wmParams.gravity = Gravity.LEFT | Gravity.TOP; }/** * 布局文件id,这里是用不到的,但还是建议填写,方便跳转到布局管理 * * @return */ protected abstract int getLayoutId(); /** * 注册需要使用的控件 */ protected abstract void initView(); protected abstract void onCreateSuspension(); /** * 根据id快速找到控件 * * @param id * @param < E> * @return */ public final < E extends View> E findView(int id) { try { return (E) view.findViewById(id); } catch (ClassCastException ex) { throw ex; } }/** * 根据id快速找到控件 * * @param id * @param onClickListener * @param < E> * @return */ public final < E extends View> E findView(int id, View.OnClickListener onClickListener) { E e = findView(id); e.setOnClickListener(onClickListener); return e; }/** * 对windowManager进行设置 * * @return */ public WindowManager.LayoutParams getParams() { wmParams = new WindowManager.LayoutParams(); //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上 //wmParams.type = LayoutParams.TYPE_PHONE; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } else { wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } //wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; //设置图片格式,效果为背景透明 wmParams.format = PixelFormat.RGBA_8888; //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) //wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE; //设置可以显示在状态栏上 wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; return wmParams; }/** * 全屏显示悬浮视图 */ public void showSuspend() { showSuspend(0, 0, true); }/** * 显示悬浮视图 * * @param sizeEntity * @param isMatchParent 是否全屏显示 */ public void showSuspend(SizeEntity sizeEntity, boolean isMatchParent) { if (sizeEntity != null) { showSuspend(sizeEntity.getWidth(), sizeEntity.getHeight(), isMatchParent); } }/** * 显示悬浮视图 * * @param width * @param height */ public void showSuspend(int width, int height, boolean isMatchParent) { //设置悬浮窗口长宽数据 if (isMatchParent) { wmParams.width = WindowManager.LayoutParams.MATCH_PARENT; wmParams.height = WindowManager.LayoutParams.MATCH_PARENT; } else { wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; } //悬浮窗的开始位置,读取缓存 wmParams.x = width; wmParams.y = height; if (isShowing) { removeView(); } mWindowManager.addView(view, wmParams); isShowing = true; }/** * 更新当前视图的位置 * * @param x 更新后的X轴的增量 * @param y 更新后的Y轴的增量 */ public void updateSuspend(int x, int y) { if (view != null) { //必须是当前显示的视图才给更新 WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) view.getLayoutParams(); layoutParams.x += x; layoutParams.y += y; mWindowManager.updateViewLayout(view, layoutParams); } }/** * 移除当前悬浮窗 */ public void dismissSuspend() { if (view != null) { mWindowManager.removeView(view); isShowing = false; if (onSuspendDismissListener != null) { onSuspendDismissListener.onDismiss(); } } }public Context getContext() { return context; }public View getView() { return view; }/** * 是否正在显示 * * @return */ public boolean isShowing() { return isShowing; }/** * 移除弹窗的时候回调 * * @param onSuspendDismissListener */ public void setOnSuspendDismissListener(OnSuspendDismissListener onSuspendDismissListener) { this.onSuspendDismissListener = onSuspendDismissListener; }public interface OnSuspendDismissListener { public void onDismiss(); } }

还有里面用到的一个size类:
/** * 宽高实体 * Created by 她叫我小渝 on 2016/11/4. */public class SizeEntity { private int width; private int height; public SizeEntity(){} public SizeEntity(int width,int height){ setWidth(width); setHeight(height); }public int getWidth() { return width; }public void setWidth(int width) { this.width = width; }public int getHeight() { return height; }public void setHeight(int height) { this.height = height; } }

3、定制视图和使用要实现的逻辑是,显示一个悬浮球,然后可以拖动移动悬浮球的位置,效果图:
Android 悬浮窗悬浮球开发

文章图片
? ? ? ? ??
Android 悬浮窗悬浮球开发

文章图片

然后新建一个类,LogoSuspend继承BaseSuspend,里面引用到了一些工具类就不贴出来了,用到的地方我会加上注释
/** * 悬浮球 * Created by 她叫我小渝 on 2017/1/1. */public class LogoSuspend extends BaseSuspend {/** * ui */ private ImageView ivLogo; /** * 变量 */ private int width, height; private float mStartX, mStartY, mStopX, mStopY, touchStartX, touchStartY; private long touchStartTime; /** * 接口 */ private View.OnClickListener onClickListener; public LogoSuspend(Context context) { super(context); }@Override protected int getLayoutId() { return R.layout.suspend_logo; }@Override protected void initView() { ivLogo = findView(R.id.iv_logo); ivLogo.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { final int action = event.getAction(); mStopX = event.getRawX(); mStopY = event.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: // 以当前父视图左上角为原点 mStartX = event.getRawX(); mStartY = event.getRawY(); touchStartX = event.getRawX(); touchStartY = event.getRawY(); touchStartTime = DateUtil.getTimeForLong(); //获取当前时间戳 break; case MotionEvent.ACTION_MOVE: width = (int) (mStopX - mStartX); height = (int) (mStopY - mStartY); mStartX = mStopX; mStartY = mStopY; updateSuspend(width, height); break; case MotionEvent.ACTION_UP: width = (int) (mStopX - mStartX); height = (int) (mStopY - mStartY); updateSuspend(width, height); WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) getView().getLayoutParams(); SuspensionCache.setSuspendSize(getContext(), new SizeEntity(layoutParams.x + width, layoutParams.y + height)); //缓存一下当前位置 if ((mStopX - touchStartX) < 30 & & (mStartY - touchStartY) < 30 & & (DateUtil.getTimeForLong() - touchStartTime) < 300) { //左右上下移动距离不超过30的,并且按下和抬起时间少于300毫秒,算是单击事件,进行回调 if (onClickListener != null) { onClickListener.onClick(view); } } break; } return true; } }); }@Override protected void onCreateSuspension() {}/** * 设置点击监听 * * @param onClickListener */ public void setOnClickListener(View.OnClickListener onClickListener) { this.onClickListener = onClickListener; } }

布局文件syspend_logo.xml
< RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rly_bg" android:layout_width="wrap_content" android:layout_height="wrap_content"> < ImageView android:id="@+id/iv_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="https://www.songbingjia.com/android/@drawable/ic_home_add_normal" /> < /RelativeLayout>

因为Activity是有生命周期的,所以打开悬浮窗的Context上下文,不要用Activity的,而是用Service的
创建并注册一个Service,然后在onCreate方法中执行调用代码就好
@Override public void onCreate() { super.onCreate(); ALog.e("服务已创建"); if (logoSuspend == null) { logoSuspend = new LogoSuspend(this); } logoSuspend.showSuspend(SuspensionCache.getSuspendSize(this), false); //从缓存中提取上一次显示的位置 logoSuspend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //处理单击事件 } }); }

4、废话????上面的例子,其实还是比较简单的,但一般开发对于悬浮球的需求并不算很大,Base类的话,目前只是最基础的东西,在开发的过程中,需要用到什么了再往里面加就好,问题不大。
????目前代码支持同时显示多个悬浮窗、悬浮球,主要用于在于悬浮窗交互的时候,直接弹出其他的交互界面(也是以悬浮窗的状态出现),但建议每一个页面都有关闭按钮或者做返回键关闭的相关操作,毕竟是显示在最前端的,要是关不掉就点哪里都没用,只能是强制关机了…………()& @()……()@#*¥)()@*#…………@#)()*¥)()…………
【Android 悬浮窗悬浮球开发】????


    推荐阅读