安卓方案类-应用内悬浮窗适配方案实战

作者

大家好,我叫小鑫,也可以叫我蜡笔小鑫?;

本人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();
}
@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 * 可拖拽贴边的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() {
@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);

    }
}
/** * @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);
}
@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();
        }
    }
}
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() {
@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);
    }
}
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哈,实际使用还需要考虑刘海屏的问题,本文暂未涉及

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY8gMSBS' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片