作者
大家好,我叫小鑫,也可以叫我蜡笔小鑫?;
本人17年毕业于中山大学,于2018年7月加入37手游安卓团队,曾经就职于久邦数码担任安卓开发工程师;
目前是37手游安卓团队的海外负责人,负责相关业务开发;同时兼顾一些基础建设相关工作。
背景
游戏内的悬浮窗通常情况下只出现在游戏内,用做切换账号、客服中心等功能的快速入口。本文将介绍几种实现方案,以及我们踩过的坑
1、方案一:应用外悬浮窗+栈顶权限/生命周期回调
通常实现悬浮窗,首先考虑到的会是要使用悬浮窗权限,用WindowManager在设备界面上addView实现(UI层级较高,应用外显示)
1、弹出悬浮窗需要用到悬浮窗权限
<!--悬浮窗权限--><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><!--悬浮窗权限--> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><!--悬浮窗权限--> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
2、判断悬浮窗游戏内外显示
方式一:使用栈顶权限获取当前
//需要声明权限<uses-permission android:name="android.permission.GET_TASKS" />//判断当前是否在后台private boolean isAppIsInBackground(Context context) {boolean isInBackground = true;ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses();for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {//前台程序if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {for (String activeProcess : processInfo.pkgList) {if (activeProcess.equals(context.getPackageName())) {isInBackground = false;}}}}} else {List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);ComponentName componentInfo = taskInfo.get(0).topActivity;if (componentInfo.getPackageName().equals(context.getPackageName())) {isInBackground = false;}}return isInBackground;}//需要声明权限 <uses-permission android:name="android.permission.GET_TASKS" /> //判断当前是否在后台 private boolean isAppIsInBackground(Context context) { boolean isInBackground = true; ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) { List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { //前台程序 if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { for (String activeProcess : processInfo.pkgList) { if (activeProcess.equals(context.getPackageName())) { isInBackground = false; } } } } } else { List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1); ComponentName componentInfo = taskInfo.get(0).topActivity; if (componentInfo.getPackageName().equals(context.getPackageName())) { isInBackground = false; } } return isInBackground; }//需要声明权限 <uses-permission android:name="android.permission.GET_TASKS" /> //判断当前是否在后台 private boolean isAppIsInBackground(Context context) { boolean isInBackground = true; ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) { List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { //前台程序 if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { for (String activeProcess : processInfo.pkgList) { if (activeProcess.equals(context.getPackageName())) { isInBackground = false; } } } } } else { List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1); ComponentName componentInfo = taskInfo.get(0).topActivity; if (componentInfo.getPackageName().equals(context.getPackageName())) { isInBackground = false; } } return isInBackground; }
这里考虑到这种方案网上有很多具体案例,在这里就不实现了。但是这种方案有如下缺点:
1、适配问题,悬浮窗权限在不同设备上由于不同产商实现不同,适配难。
2、向用户申请权限,打开率较低,体验较差
2、方案二:addContentView实现
原理:Activity的接口中除了我们常用的setContentView接口外,还有addContentView接口。利用该接口可以在Activity上添加View。
这里你可能会问:
1、那只能在一个Activity上添加吧?
没错,是只能在当前Activity上添加,但是由于游戏通常也就在一个Activity跑,因此基本上是可以接受的。
2、只add一个view,那拖动怎么实现?
LayoutParams params = new LayoutParams(mWidth, mHeight);params.setMargins(mLeft, mTop, 0, 0);setLayoutParams(params);LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, mTop, 0, 0); setLayoutParams(params);LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, mTop, 0, 0); setLayoutParams(params);
通过更新LayoutParams调整子View在父View中的位置就能实现
具体代码如下:
/*** @author zhuxiaoxin* 可拖拽贴边的view*/public class DragViewLayout extends RelativeLayout {//手指拖拽得到的位置int mLeft, mRight, mTop, mBottom;//view所在的位置int mLastX, mLastY;/*** 屏幕宽度|高度*/int mScreenWidth, mScreenHeight;/*** view的宽度|高度*/int mWidth, mHeight;/*** 是否在拖拽过程中*/boolean isDrag = false;/*** 系统最小滑动距离* @param context*/int mTouchSlop = 0;public DragViewLayout(Context context) {this(context, null);}public DragViewLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overrideprotected void onFinishInflate() {super.onFinishInflate();}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:mLeft = getLeft();mRight = getRight();mTop = getTop();mBottom = getBottom();mLastX = (int) event.getRawX();mLastY = (int) event.getRawY();break;case MotionEvent.ACTION_MOVE:int x = (int) event.getRawX();int y = (int) event.getRawY();int dx = x - mLastX;int dy = y - mLastY;if (Math.abs(dx) > mTouchSlop) {isDrag = true;}mLeft += dx;mRight += dx;mTop += dy;mBottom += dy;if (mLeft < 0) {mLeft = 0;mRight = mWidth;}if (mRight >= mScreenWidth) {mRight = mScreenWidth;mLeft = mScreenWidth - mWidth;}if (mTop < 0) {mTop = 0;mBottom = getHeight();}if (mBottom > mScreenHeight) {mBottom = mScreenHeight;mTop = mScreenHeight - mHeight;}mLastX = x;mLastY = y;//根据拖动举例设置view的margin参数,实现拖动效果LayoutParams params = new LayoutParams(mWidth, mHeight);params.setMargins(mLeft, mTop, 0, 0);setLayoutParams(params);break;case MotionEvent.ACTION_UP://手指抬起,执行贴边动画if (isDrag) {startAnim();isDrag = false;}break;}return super.dispatchTouchEvent(event);}//执行贴边动画private void startAnim(){ValueAnimator valueAnimator;if (mLeft < mScreenWidth / 2) {valueAnimator = ValueAnimator.ofInt(mLeft, 0);} else {valueAnimator = ValueAnimator.ofInt(mLeft, mScreenWidth - mWidth);}//动画执行时间valueAnimator.setDuration(100);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mLeft = (int)animation.getAnimatedValue();//动画执行依然是使用设置margin参数实现LayoutParams params = new LayoutParams(mWidth, mHeight);params.setMargins(mLeft, getTop(), 0, 0);setLayoutParams(params);}});valueAnimator.start();}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (mWidth == 0) {//获取view的高宽mWidth = getWidth();mHeight = getHeight();}}}/** * @author zhuxiaoxin * 可拖拽贴边的view */ public class DragViewLayout extends RelativeLayout { //手指拖拽得到的位置 int mLeft, mRight, mTop, mBottom; //view所在的位置 int mLastX, mLastY; /** * 屏幕宽度|高度 */ int mScreenWidth, mScreenHeight; /** * view的宽度|高度 */ int mWidth, mHeight; /** * 是否在拖拽过程中 */ boolean isDrag = false; /** * 系统最小滑动距离 * @param context */ int mTouchSlop = 0; public DragViewLayout(Context context) { this(context, null); } public DragViewLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScreenWidth = context.getResources().getDisplayMetrics().widthPixels; mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override protected void onFinishInflate() { super.onFinishInflate(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mLeft = getLeft(); mRight = getRight(); mTop = getTop(); mBottom = getBottom(); mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int x = (int) event.getRawX(); int y = (int) event.getRawY(); int dx = x - mLastX; int dy = y - mLastY; if (Math.abs(dx) > mTouchSlop) { isDrag = true; } mLeft += dx; mRight += dx; mTop += dy; mBottom += dy; if (mLeft < 0) { mLeft = 0; mRight = mWidth; } if (mRight >= mScreenWidth) { mRight = mScreenWidth; mLeft = mScreenWidth - mWidth; } if (mTop < 0) { mTop = 0; mBottom = getHeight(); } if (mBottom > mScreenHeight) { mBottom = mScreenHeight; mTop = mScreenHeight - mHeight; } mLastX = x; mLastY = y; //根据拖动举例设置view的margin参数,实现拖动效果 LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, mTop, 0, 0); setLayoutParams(params); break; case MotionEvent.ACTION_UP: //手指抬起,执行贴边动画 if (isDrag) { startAnim(); isDrag = false; } break; } return super.dispatchTouchEvent(event); } //执行贴边动画 private void startAnim(){ ValueAnimator valueAnimator; if (mLeft < mScreenWidth / 2) { valueAnimator = ValueAnimator.ofInt(mLeft, 0); } else { valueAnimator = ValueAnimator.ofInt(mLeft, mScreenWidth - mWidth); } //动画执行时间 valueAnimator.setDuration(100); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mLeft = (int)animation.getAnimatedValue(); //动画执行依然是使用设置margin参数实现 LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, getTop(), 0, 0); setLayoutParams(params); } }); valueAnimator.start(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mWidth == 0) { //获取view的高宽 mWidth = getWidth(); mHeight = getHeight(); } } }/** * @author zhuxiaoxin * 可拖拽贴边的view */ public class DragViewLayout extends RelativeLayout { //手指拖拽得到的位置 int mLeft, mRight, mTop, mBottom; //view所在的位置 int mLastX, mLastY; /** * 屏幕宽度|高度 */ int mScreenWidth, mScreenHeight; /** * view的宽度|高度 */ int mWidth, mHeight; /** * 是否在拖拽过程中 */ boolean isDrag = false; /** * 系统最小滑动距离 * @param context */ int mTouchSlop = 0; public DragViewLayout(Context context) { this(context, null); } public DragViewLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScreenWidth = context.getResources().getDisplayMetrics().widthPixels; mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override protected void onFinishInflate() { super.onFinishInflate(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mLeft = getLeft(); mRight = getRight(); mTop = getTop(); mBottom = getBottom(); mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int x = (int) event.getRawX(); int y = (int) event.getRawY(); int dx = x - mLastX; int dy = y - mLastY; if (Math.abs(dx) > mTouchSlop) { isDrag = true; } mLeft += dx; mRight += dx; mTop += dy; mBottom += dy; if (mLeft < 0) { mLeft = 0; mRight = mWidth; } if (mRight >= mScreenWidth) { mRight = mScreenWidth; mLeft = mScreenWidth - mWidth; } if (mTop < 0) { mTop = 0; mBottom = getHeight(); } if (mBottom > mScreenHeight) { mBottom = mScreenHeight; mTop = mScreenHeight - mHeight; } mLastX = x; mLastY = y; //根据拖动举例设置view的margin参数,实现拖动效果 LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, mTop, 0, 0); setLayoutParams(params); break; case MotionEvent.ACTION_UP: //手指抬起,执行贴边动画 if (isDrag) { startAnim(); isDrag = false; } break; } return super.dispatchTouchEvent(event); } //执行贴边动画 private void startAnim(){ ValueAnimator valueAnimator; if (mLeft < mScreenWidth / 2) { valueAnimator = ValueAnimator.ofInt(mLeft, 0); } else { valueAnimator = ValueAnimator.ofInt(mLeft, mScreenWidth - mWidth); } //动画执行时间 valueAnimator.setDuration(100); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mLeft = (int)animation.getAnimatedValue(); //动画执行依然是使用设置margin参数实现 LayoutParams params = new LayoutParams(mWidth, mHeight); params.setMargins(mLeft, getTop(), 0, 0); setLayoutParams(params); } }); valueAnimator.start(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mWidth == 0) { //获取view的高宽 mWidth = getWidth(); mHeight = getHeight(); } } }
/*** @author zhuxiaoxin* 37悬浮窗基础view*/public class SqAddFloatView extends DragViewLayout {private RelativeLayout mFloatContainer;public SqAddFloatView(final Context context, final int floatImgId) {super(context);setClickable(true);final ImageView floatView = new ImageView(context);floatView.setImageResource(floatImgId);floatView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show();}});LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);addView(floatView, params);}public void show(Activity activity) {FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);if(mFloatContainer == null) {mFloatContainer = new RelativeLayout(activity);}RelativeLayout.LayoutParams floatViewParams = new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);floatViewParams.setMargins(0, (int) (mScreenHeight * 0.4), 0, 0);mFloatContainer.addView(this, floatViewParams);activity.addContentView(mFloatContainer, params);}}/** * @author zhuxiaoxin * 37悬浮窗基础view */ public class SqAddFloatView extends DragViewLayout { private RelativeLayout mFloatContainer; public SqAddFloatView(final Context context, final int floatImgId) { super(context); setClickable(true); final ImageView floatView = new ImageView(context); floatView.setImageResource(floatImgId); floatView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show(); } }); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); addView(floatView, params); } public void show(Activity activity) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); if(mFloatContainer == null) { mFloatContainer = new RelativeLayout(activity); } RelativeLayout.LayoutParams floatViewParams = new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); floatViewParams.setMargins(0, (int) (mScreenHeight * 0.4), 0, 0); mFloatContainer.addView(this, floatViewParams); activity.addContentView(mFloatContainer, params); } }/** * @author zhuxiaoxin * 37悬浮窗基础view */ public class SqAddFloatView extends DragViewLayout { private RelativeLayout mFloatContainer; public SqAddFloatView(final Context context, final int floatImgId) { super(context); setClickable(true); final ImageView floatView = new ImageView(context); floatView.setImageResource(floatImgId); floatView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show(); } }); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); addView(floatView, params); } public void show(Activity activity) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); if(mFloatContainer == null) { mFloatContainer = new RelativeLayout(activity); } RelativeLayout.LayoutParams floatViewParams = new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); floatViewParams.setMargins(0, (int) (mScreenHeight * 0.4), 0, 0); mFloatContainer.addView(this, floatViewParams); activity.addContentView(mFloatContainer, params); } }
在Activity中使用
SqAddFloatView(this, R.mipmap.ic_launcher).show(this)SqAddFloatView(this, R.mipmap.ic_launcher).show(this)SqAddFloatView(this, R.mipmap.ic_launcher).show(this)
3、方案三:WindowManager+应用内层级实现
WindowManger中的层级有如下两个(其实是一样的~)可以实现在Activity上增加View
/*** Start of types of sub-windows. The {@link #token} of these windows* must be set to the window they are attached to. These types of* windows are kept next to their attached window in Z-order, and their* coordinate space is relative to their attached window.*/public static final int FIRST_SUB_WINDOW = 1000;/*** Window type: a panel on top of an application window. These windows* appear on top of their attached window.*/public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;/** * Start of types of sub-windows. The {@link #token} of these windows * must be set to the window they are attached to. These types of * windows are kept next to their attached window in Z-order, and their * coordinate space is relative to their attached window. */ public static final int FIRST_SUB_WINDOW = 1000; /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;/** * Start of types of sub-windows. The {@link #token} of these windows * must be set to the window they are attached to. These types of * windows are kept next to their attached window in Z-order, and their * coordinate space is relative to their attached window. */ public static final int FIRST_SUB_WINDOW = 1000; /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
具体实现时,WindowManger相关的核心代码如下:
public void show() {floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,//最最重要的一句 WindowManager.LayoutParams.FIRST_SUB_WINDOW,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,PixelFormat.RGBA_8888);floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;floatLayoutParams.x = mMinWidth;floatLayoutParams.y = (int)(mScreenHeight * 0.4);mWindowManager.addView(this, floatLayoutParams);}public void show() { floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, //最最重要的一句 WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.RGBA_8888); floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; floatLayoutParams.x = mMinWidth; floatLayoutParams.y = (int)(mScreenHeight * 0.4); mWindowManager.addView(this, floatLayoutParams); }public void show() { floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, //最最重要的一句 WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.RGBA_8888); floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; floatLayoutParams.x = mMinWidth; floatLayoutParams.y = (int)(mScreenHeight * 0.4); mWindowManager.addView(this, floatLayoutParams); }
添加完view如何更新位置?
使用WindowManager的updateViewLayout方法
mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams);mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams);mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams);
完整代码如下:
DragViewLayout:
public class DragViewLayout extends RelativeLayout {//view所在位置int mLastX, mLastY;//屏幕高宽int mScreenWidth, mScreenHeight;//view高宽int mWidth, mHeight;/*** 是否在拖拽过程中*/boolean isDrag = false;/*** 系统最小滑动距离* @param context*/int mTouchSlop = 0;WindowManager.LayoutParams floatLayoutParams;WindowManager mWindowManager;//手指触摸位置private float xInScreen;private float yInScreen;private float xInView;public float yInView;public DragViewLayout(Context context) {this(context, null);}public DragViewLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:mLastX = (int) event.getRawX();mLastY = (int) event.getRawY();yInView = event.getY();xInView = event.getX();xInScreen = event.getRawX();yInScreen = event.getRawY();break;case MotionEvent.ACTION_MOVE:int dx = (int) event.getRawX() - mLastX;int dy = (int) event.getRawY() - mLastY;if (Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) {isDrag = true;}xInScreen = event.getRawX();yInScreen = event.getRawY();mLastX = (int) event.getRawX();mLastY = (int) event.getRawY();//拖拽时调用WindowManager updateViewLayout更新悬浮球位置updateFloatPosition(false);break;case MotionEvent.ACTION_UP:if (isDrag) {//执行贴边startAnim();isDrag = false;}break;default:break;}return super.dispatchTouchEvent(event);}//更新悬浮球位置private void updateFloatPosition(boolean isUp) {int x = (int) (xInScreen - xInView);int y = (int) (yInScreen - yInView);if(isUp) {x = isRightFloat() ? mScreenWidth : 0;}if(y < 0) {y = 0;}if(y > mScreenHeight - mHeight) {y = mScreenHeight - mHeight;}floatLayoutParams.x = x;floatLayoutParams.y = y;//更新位置mWindowManager.updateViewLayout(this, floatLayoutParams);}/*** 是否靠右边悬浮* @return*/boolean isRightFloat() {return xInScreen > mScreenWidth / 2;}//执行贴边动画private void startAnim(){ValueAnimator valueAnimator;if (floatLayoutParams.x < mScreenWidth / 2) {valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, 0);} else {valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, mScreenWidth - mWidth);}valueAnimator.setDuration(200);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {floatLayoutParams.x = (int)animation.getAnimatedValue();mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams);}});valueAnimator.start();}//悬浮球显示public void show() {floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.FIRST_SUB_WINDOW,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,PixelFormat.RGBA_8888);floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;floatLayoutParams.x = 0;floatLayoutParams.y = (int)(mScreenHeight * 0.4);mWindowManager.addView(this, floatLayoutParams);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (mWidth == 0) {//获取悬浮球高宽mWidth = getWidth();mHeight = getHeight();}}}public class DragViewLayout extends RelativeLayout { //view所在位置 int mLastX, mLastY; //屏幕高宽 int mScreenWidth, mScreenHeight; //view高宽 int mWidth, mHeight; /** * 是否在拖拽过程中 */ boolean isDrag = false; /** * 系统最小滑动距离 * @param context */ int mTouchSlop = 0; WindowManager.LayoutParams floatLayoutParams; WindowManager mWindowManager; //手指触摸位置 private float xInScreen; private float yInScreen; private float xInView; public float yInView; public DragViewLayout(Context context) { this(context, null); } public DragViewLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScreenWidth = context.getResources().getDisplayMetrics().widthPixels; mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); } @Override protected void onFinishInflate() { super.onFinishInflate(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); yInView = event.getY(); xInView = event.getX(); xInScreen = event.getRawX(); yInScreen = event.getRawY(); break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - mLastX; int dy = (int) event.getRawY() - mLastY; if (Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) { isDrag = true; } xInScreen = event.getRawX(); yInScreen = event.getRawY(); mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); //拖拽时调用WindowManager updateViewLayout更新悬浮球位置 updateFloatPosition(false); break; case MotionEvent.ACTION_UP: if (isDrag) { //执行贴边 startAnim(); isDrag = false; } break; default: break; } return super.dispatchTouchEvent(event); } //更新悬浮球位置 private void updateFloatPosition(boolean isUp) { int x = (int) (xInScreen - xInView); int y = (int) (yInScreen - yInView); if(isUp) { x = isRightFloat() ? mScreenWidth : 0; } if(y < 0) { y = 0; } if(y > mScreenHeight - mHeight) { y = mScreenHeight - mHeight; } floatLayoutParams.x = x; floatLayoutParams.y = y; //更新位置 mWindowManager.updateViewLayout(this, floatLayoutParams); } /** * 是否靠右边悬浮 * @return */ boolean isRightFloat() { return xInScreen > mScreenWidth / 2; } //执行贴边动画 private void startAnim(){ ValueAnimator valueAnimator; if (floatLayoutParams.x < mScreenWidth / 2) { valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, 0); } else { valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, mScreenWidth - mWidth); } valueAnimator.setDuration(200); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { floatLayoutParams.x = (int)animation.getAnimatedValue(); mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams); } }); valueAnimator.start(); } //悬浮球显示 public void show() { floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.RGBA_8888); floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; floatLayoutParams.x = 0; floatLayoutParams.y = (int)(mScreenHeight * 0.4); mWindowManager.addView(this, floatLayoutParams); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mWidth == 0) { //获取悬浮球高宽 mWidth = getWidth(); mHeight = getHeight(); } } }public class DragViewLayout extends RelativeLayout { //view所在位置 int mLastX, mLastY; //屏幕高宽 int mScreenWidth, mScreenHeight; //view高宽 int mWidth, mHeight; /** * 是否在拖拽过程中 */ boolean isDrag = false; /** * 系统最小滑动距离 * @param context */ int mTouchSlop = 0; WindowManager.LayoutParams floatLayoutParams; WindowManager mWindowManager; //手指触摸位置 private float xInScreen; private float yInScreen; private float xInView; public float yInView; public DragViewLayout(Context context) { this(context, null); } public DragViewLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScreenWidth = context.getResources().getDisplayMetrics().widthPixels; mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); } @Override protected void onFinishInflate() { super.onFinishInflate(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); yInView = event.getY(); xInView = event.getX(); xInScreen = event.getRawX(); yInScreen = event.getRawY(); break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - mLastX; int dy = (int) event.getRawY() - mLastY; if (Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) { isDrag = true; } xInScreen = event.getRawX(); yInScreen = event.getRawY(); mLastX = (int) event.getRawX(); mLastY = (int) event.getRawY(); //拖拽时调用WindowManager updateViewLayout更新悬浮球位置 updateFloatPosition(false); break; case MotionEvent.ACTION_UP: if (isDrag) { //执行贴边 startAnim(); isDrag = false; } break; default: break; } return super.dispatchTouchEvent(event); } //更新悬浮球位置 private void updateFloatPosition(boolean isUp) { int x = (int) (xInScreen - xInView); int y = (int) (yInScreen - yInView); if(isUp) { x = isRightFloat() ? mScreenWidth : 0; } if(y < 0) { y = 0; } if(y > mScreenHeight - mHeight) { y = mScreenHeight - mHeight; } floatLayoutParams.x = x; floatLayoutParams.y = y; //更新位置 mWindowManager.updateViewLayout(this, floatLayoutParams); } /** * 是否靠右边悬浮 * @return */ boolean isRightFloat() { return xInScreen > mScreenWidth / 2; } //执行贴边动画 private void startAnim(){ ValueAnimator valueAnimator; if (floatLayoutParams.x < mScreenWidth / 2) { valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, 0); } else { valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, mScreenWidth - mWidth); } valueAnimator.setDuration(200); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { floatLayoutParams.x = (int)animation.getAnimatedValue(); mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams); } }); valueAnimator.start(); } //悬浮球显示 public void show() { floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.RGBA_8888); floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; floatLayoutParams.x = 0; floatLayoutParams.y = (int)(mScreenHeight * 0.4); mWindowManager.addView(this, floatLayoutParams); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mWidth == 0) { //获取悬浮球高宽 mWidth = getWidth(); mHeight = getHeight(); } } }
悬浮窗View
public class SqWindowManagerFloatView extends DragViewLayout {public SqWindowManagerFloatView(final Context context, final int floatImgId) {super(context);setClickable(true);final ImageView floatView = new ImageView(context);floatView.setImageResource(floatImgId);floatView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show();}});LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);addView(floatView, params);}}public class SqWindowManagerFloatView extends DragViewLayout { public SqWindowManagerFloatView(final Context context, final int floatImgId) { super(context); setClickable(true); final ImageView floatView = new ImageView(context); floatView.setImageResource(floatImgId); floatView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show(); } }); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); addView(floatView, params); } }public class SqWindowManagerFloatView extends DragViewLayout { public SqWindowManagerFloatView(final Context context, final int floatImgId) { super(context); setClickable(true); final ImageView floatView = new ImageView(context); floatView.setImageResource(floatImgId); floatView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "点击了悬浮球", Toast.LENGTH_SHORT).show(); } }); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); addView(floatView, params); } }
使用:
SqWindowManagerFloatView(this, R.mipmap.float_icon).show()SqWindowManagerFloatView(this, R.mipmap.float_icon).show()SqWindowManagerFloatView(this, R.mipmap.float_icon).show()
4、小结
1、方案一需要用到多个权限,显然是不合适的。
2、方案二简单方便,但是用到了Activity的addContentView方法,在某些游戏引擎上使用会有问题。因为有些游戏引擎不是在Activity上跑的,而是在NativeActivity上跑
3、方案三是我们当前采用的方案,目前还暂未发现有显示不出来之类的问题~
4、本文讲述的方案只是Demo哈,实际使用还需要考虑刘海屏的问题,本文暂未涉及