Last active
August 18, 2020 08:16
-
-
Save spiritedRunning/11ffc7314c38515e309831b6c449fe4c to your computer and use it in GitHub Desktop.
Implement of floating window
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FloatingWindow extends FrameLayout { | |
private static final String TAG = "FloatCallView"; | |
private Context mContext; | |
private int mediaType; | |
public static int viewHeight; | |
public static int viewWidth; | |
// View中X坐标 | |
private float xInView; | |
// View中Y坐标 | |
private float yInView; | |
// 当前X坐标 | |
private float xInScreen; | |
// 当前Y坐标 | |
private float yInScreen; | |
// 记录移动前X坐标 | |
private float mStartX; | |
// 记录移动前Y坐标 | |
private float mStartY; | |
// 视频SurfaceView | |
private SurfaceView videoSurfaceView; | |
private FrameLayout videoContainerLayout; | |
private OnClickListener mOnClickListner; | |
private View mRootView; | |
// 时间状态显示 | |
private TextView timeStateText; | |
private WindowManager wm; | |
private WindowManager.LayoutParams wmParams; | |
// 当前是否处于呼状态 | |
private boolean isCalling; | |
public FloatCallView(Context context, int resId, int mediaType) { | |
super(context); | |
this.mContext = context; | |
this.mediaType = TangSDKInstance.getInstance().isVideoCallOpened() ? Constants.CALL_VIDEO_STATE : Constants.CALL_VOICE_STATE; | |
wm = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE); | |
LayoutInflater.from(mContext).inflate(resId, this); | |
initView(); | |
} | |
private void initView() { | |
if (mediaType == Constants.CALL_VOICE_STATE) { | |
mRootView = findViewById(R.id.voice_call_layout); | |
timeStateText = (TextView) findViewById(R.id.call_state_prompt_text); | |
} else if (mediaType == Constants.CALL_VIDEO_STATE) { | |
mRootView = findViewById(R.id.video_call_layout); | |
// videoSurfaceView = (SurfaceView) findViewById(R.id.video_stream_surfaceview); | |
} | |
} | |
public SurfaceView getVideoSurface() { | |
if (videoSurfaceView != null) { | |
return videoSurfaceView; | |
} | |
return null; | |
} | |
public void setParams(WindowManager.LayoutParams params) { | |
wmParams = params; | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
// 获取状态栏高度 | |
Rect frame = new Rect(); | |
getWindowVisibleDisplayFrame(frame); | |
int statusBar = frame.top; | |
Log.d(TAG, "statusBar: " + statusBar); | |
int screenWidth = wm.getDefaultDisplay().getWidth(); | |
int screenHeight = wm.getDefaultDisplay().getHeight(); | |
// 获取相对屏幕的坐标,即以屏幕左上角为原点 | |
xInScreen = event.getRawX(); | |
yInScreen = event.getRawY() - statusBar; | |
Log.i("onTouchEvent", "x: " + xInScreen + ", y: " + yInScreen); | |
switch (event.getAction()) { | |
case MotionEvent.ACTION_DOWN: | |
// 获取相对View的坐标,即以此View左上角为原点 | |
xInView = event.getX(); | |
yInView = event.getY(); | |
mStartX = xInScreen; | |
mStartY = yInScreen; | |
Log.i("ACTION_DOWN", "xInView: " + xInView + ", mTouchStartY: " + yInView); | |
break; | |
case MotionEvent.ACTION_MOVE: | |
xInScreen = event.getRawX(); | |
yInScreen = event.getRawY() - statusBar; | |
Log.i("ACTION_MOVE", "xInScreen: " + xInScreen + ", yInScreen: " + yInScreen + ", xInView: " + xInView + ", yInView: " + yInView); | |
updateViewPosition(); | |
break; | |
case MotionEvent.ACTION_UP: | |
if (xInScreen == mStartX && yInScreen == mStartY) { | |
if (mOnClickListner != null) { | |
mOnClickListner.onClick(this); | |
Log.i(TAG, "click floating window"); | |
} | |
} | |
if (xInScreen < screenWidth / 2) { | |
xInScreen = 0; | |
} else { | |
xInScreen = screenWidth; | |
} | |
updateViewPosition(); | |
break; | |
} | |
return true; | |
} | |
@Override | |
public void setOnClickListener(OnClickListener l) { | |
this.mOnClickListner = l; | |
} | |
private void updateViewPosition() { | |
wmParams.x = (int) (xInScreen - xInView); | |
wmParams.y = (int) (yInScreen - yInView); | |
wm.updateViewLayout(this, wmParams); | |
} | |
} | |
/** | |
* @brief 创建悬浮层用于显示通话状态,视频图像 | |
*/ | |
public void createFloatingWindow() { | |
floatCallView = new FloatCallView(this); | |
floatCallView.setOnClickListener(this); | |
int screenWidth = wm.getDefaultDisplay().getWidth(); | |
int screenHeight = wm.getDefaultDisplay().getHeight(); | |
wmParams = ((MyApplication) getApplication()).getLayoutParams(); | |
// 设置悬浮窗类型 | |
if (DeviceUtil.isSpecialDevice(DeviceUtil.DEVICE_XIAOMI) || | |
DeviceUtil.isSpecialDevice(DeviceUtil.DEVICE_MEIZU)) { | |
wmParams.type = WindowManager.LayoutParams.TYPE_TOAST; | |
} else { | |
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; | |
} | |
// 设置图片格式,效果为背景透明 | |
wmParams.format = PixelFormat.RGBA_8888; | |
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | |
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; | |
// 悬浮窗的对齐方式. 便于调整坐标 | |
wmParams.gravity = Gravity.LEFT | Gravity.TOP; | |
// 以屏幕左上角为原点, 设置悬浮层初始位置 | |
wmParams.x = screenWidth; | |
wmParams.y = screenHeight / 2; | |
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; | |
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; | |
floatCallView.setParams(wmParams); | |
wm.addView(floatCallView, wmParams); | |
} | |
private void removeFloatView() { | |
if (floatCallView != null) { | |
wm.removeView(floatCallView); | |
floatCallView = null; | |
} | |
} | |
/** | |
* @brief 判断悬浮窗权限是否打开 | |
* @return true 权限已打开 false 无权限 | |
*/ | |
@TargetApi(Build.VERSION_CODES.KITKAT) | |
public static boolean invokeFloatOpAllowed(Context context) { | |
try { | |
if (getSDKVersion() >= 19) { | |
Object object = context.getSystemService(Context.APP_OPS_SERVICE); | |
if (object == null) { | |
return false; | |
} | |
Class localClass = object.getClass(); | |
Class[] arrayOfClass = new Class[3]; | |
arrayOfClass[0] = Integer.TYPE; | |
arrayOfClass[1] = Integer.TYPE; | |
arrayOfClass[2] = String.class; | |
Method method = localClass.getMethod("checkOp", arrayOfClass); | |
if (method == null) { | |
return false; | |
} | |
Object[] arrayOfObject1 = new Object[3]; | |
// AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24 | |
arrayOfObject1[0] = Integer.valueOf(24); | |
arrayOfObject1[1] = Integer.valueOf(Binder.getCallingUid()); | |
arrayOfObject1[2] = context.getPackageName(); | |
int m = ((Integer) method.invoke(object, arrayOfObject1)).intValue(); | |
boolean isAllowed = (m == AppOpsManager.MODE_ALLOWED || m == AppOpsManager.MODE_DEFAULT); | |
LogUtil.d(TAG, "isAllowed: " + m); | |
return isAllowed; | |
} | |
} catch (Throwable ex) { | |
LogUtil.e(TAG, "getFloatOpAllowed error: " + ex); | |
} | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add permission
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>