一:对于想直接看效果的,可以看看我的demo app.
链接:http://sj.qq.com/myapp/detail.htm?apkName=com.inno.backdot
二: Android 6.0 关于
SYSTEM_ALERT_WINDOW
权限申明直接报错
// 设置window type
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWinParams.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
mWinParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//原因1:type为"TYPE_TOAST"在sdk19之前不接收事件,之后可以.
//原因12:type为"TYPE_PHONE"需要"SYSTEM_ALERT_WINDOW"权限.在sdk19之前不可以直接申明使用,之后不能直接申明使用.
三:用到的技术知识点:
1\. OnTouch()的事件处理
2\. WindowManager类及其LayoutParams的常见属性的理解
3\. Handler更新UI
4\. 定时器(Timer + TimerTask)
1. OnTouch事件处理
这个网上的资料很多,这里说一些注意点:
1.获取坐标
event.getRawX()
:获取相对屏幕的坐标X(获取Y的坐标同理)event.getX()
:获取相对于容器的坐标X(获取Y的坐标同理)
2.返回值
return true
:表示事件不往下传递了return false
:表示继续传递事件
2. WindowManager类
获取方式:
mWmManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams类
this.mWinParams = new WindowManager.LayoutParams();
// 设置图片格式,效果为背景透明
mWinParams.format = PixelFormat.RGBA_8888;
// 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 调整悬浮窗显示的停靠位置为左侧置�?
mWinParams.gravity= Gravity.LEFT | Gravity.TOP;
mScreenHeight = mWmManager.getDefaultDisplay().getHeight();
// 以屏幕左上角为原点,设置x、y初始值,相对于gravity
mWinParams.x = mScreenWidth/4;
mWinParams.y = mScreenHeight/4;
// 设置悬浮窗口长宽数据
mWinParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
mWinParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;
其中需要注意的是其Gravity
属性:
注意:Gravity不是说你添加到WindowManager中的View相对屏幕的几种放置,而是说你可以设置你的 参 考 系 !
例如:mWinParams.gravity= Gravity.LEFT | Gravity.TOP;
意思是以屏幕左上角为参考系,那么屏幕左上角的坐标就是(0,0),这是你后面摆放View位置的唯一依据.当你设置为mWinParams.gravity = Gravity.CENTER;
那么你的屏幕中心为参考系,坐标(0,0).一般我们用屏幕左上角为参考系.
设置WindowManager中的View的透明度
使用:LayoutParams.alpha
属性(0.0f ~ 1.0f),1.0f不透明,0.0f全透明,源码如下:
/**
* An alpha value to apply to this entire window.
* An alpha of 1.0 means fully opaque and 0.0 means fully transparent
*/
public float alpha = 1.0f;
Handler更新UI(略)
定时器
TimerTask timerTask = new TimerTask(){其实就是一个Runnable};
看他的类:
public abstract class TimerTask implements Runnable{...}
Timer mtimer=new Timer();
使用的时候:
mtimer.schedule(timerTask,0,3);//参数1:执行的任务;参数2:延迟0毫米执行;参数3:每隔3毫秒执行一次任务;
直播工具类
/**
* Description:初始化直播弹窗工具
* Created by PangHaHa on 18-7-18.
* Copyright (c) 2018 PangHaHa All rights reserved.
*/
public class LiveUtils {
private InitParam mInitParam;
private GSVideoView gsVideoView;//播放器
private String circle_id,media_host,media_code,img_url,video_code;
private Player mPlayer;
private OnPlayListener playListener;
//布局参数.
private WindowManager.LayoutParams params;
//实例化的WindowManager.
private WindowManager windowManager;
private int statusBarHeight =-1;
private FrameLayout toucherLayout;
private ImageView imageViewClose;
private int count = 0;//点击次数
private long firstClick = 0;//第一次点击时间
private long secondClick = 0;//第二次点击时间
private float start_X = 0;
private float start_Y = 0;
// 记录上次移动的位置
private float lastX = 0;
private float lastY = 0;
private int offset;
// 是否是移动事件
boolean isMoved = false;
/**
* 两次点击时间间隔,单位毫秒
*/
private final int totalTime = 1000;
private boolean isInit = true;
public void initLive(final Context context, Map<String,String> map){
try {
circle_id = map.get("circle_id");
media_host = map.get("media_host");
media_code = map.get("media_code");
img_url = map.get("img_url");
video_code = map.get("video_code");
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//赋值WindowManager&LayoutParam.
params = new WindowManager.LayoutParams();
//设置type.系统提示型窗口,一般都在应用程序窗口之上.
if (Build.VERSION.SDK_INT >= 26) {//8.0新特性
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
//设置效果为背景透明.
params.format = PixelFormat.RGBA_8888;
//设置flags.不可聚焦及不可使用按钮对悬浮窗进行操控.
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//设置窗口坐标参考系
params.gravity = Gravity.LEFT | Gravity.TOP;
//用于检测状态栏高度.
int resourceId = context.getResources().getIdentifier("status_bar_height",
"dimen","android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
offset = DimensionUtils.dp2px(context, 2);//移动偏移量
//设置原点
params.x = getScreenWidth(context) - DimensionUtils.dp2px(context, 170);
params.y = getScreenHeight(context) - DimensionUtils.dp2px(context, 100+72) ;
//设置悬浮窗口长宽数据.
params.width = DimensionUtils.dp2px(context, 170);
params.height = DimensionUtils.dp2px(context, 100);
//获取浮动窗口视图所在布局.
toucherLayout = new FrameLayout(context);
mPlayer = new Player();
gsVideoView = new GSVideoView(context);
playListener = new OnPlayListener() {
@Override
public void onJoin(int i) {
}
@Override
public void onUserJoin(UserInfo userInfo) {
}
@Override
public void onUserLeave(UserInfo userInfo) {
}
@Override
public void onUserUpdate(UserInfo userInfo) {
}
@Override
public void onRosterTotal(int i) {
}
@Override
public void onReconnecting() {
}
@Override
public void onLeave(int i) {
}
@Override
public void onCaching(boolean b) {
}
@Override
public void onErr(int i) {
}
@Override
public void onDocSwitch(int i, String s) {
}
@Override
public void onVideoBegin() {
}
@Override
public void onVideoEnd() {
}
@Override
public void onVideoSize(int i, int i1, boolean b) {
}
@Override
public void onAudioLevel(int i) {
}
@Override
public void onPublish(boolean b) {
}
@Override
public void onSubject(String s) {
}
@Override
public void onPageSize(int i, int i1, int i2) {
}
@Override
public void onVideoDataNotify() {
}
@Override
public void onPublicMsg(long l, String s) {
}
@Override
public void onLiveText(String s, String s1) {
}
@Override
public void onRollcall(int i) {
}
@Override
public void onLottery(int i, String s) {
}
@Override
public void onFileShare(int i, String s, String s1) {
}
@Override
public void onFileShareDl(int i, String s, String s1) {
}
@Override
public void onInvite(int i, boolean b) {
}
@Override
public void onMicNotify(int i) {
}
@Override
public void onCameraNotify(int i) {
}
@Override
public void onScreenStatus(boolean b) {
}
@Override
public void onModuleFocus(int i) {
}
@Override
public void onIdcList(List<PingEntity> list) {
}
@Override
public void onThirdVote(String s) {
}
@Override
public void onRewordEnable(boolean b, boolean b1) {
}
@Override
public void onRedBagTip(RewardResult rewardResult) {
}
@Override
public void onGotoPay(PayInfo payInfo) {
}
@Override
public void onGetUserInfo(UserInfo[] userInfos) {
}
@Override
public void onLiveInfo(LiveInfo liveInfo) {
}
};
mInitParam = new InitParam();
//站点域名 如:demo.gensee.com 必需
mInitParam.setDomain(media_host);
//直播id或点播id
mInitParam.setLiveId(media_code);
//昵称,必需
mInitParam.setNickName("新浪理财师");
//如果后台设置了密码(口令),必须传入正确的密码
mInitParam.setJoinPwd(video_code);
//必须选择一种 serviceType
// 站点类型ServiceType.ST_CASTLINE 直播webcast,
// ServiceType.ST_TRAINING 培训 training
mInitParam.setServiceType(ServiceType.WEBCAST);
/**
* 设置视频View
*/
mPlayer.setGSVideoView(gsVideoView);
//加入直播房间
mPlayer.join(context,mInitParam,playListener);
toucherLayout.addView(gsVideoView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
imageViewClose = new ImageView(context);
imageViewClose.setImageDrawable(RePlugin.getPluginContext().getResources().getDrawable(R.drawable.course_icon_remove));
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(DimensionUtils.
dp2px(context, 16), DimensionUtils.dp2px(context, 16));
layoutParams.gravity = Gravity.TOP | Gravity.RIGHT;
layoutParams.rightMargin = DimensionUtils.dp2px(context, 3);
layoutParams.topMargin = DimensionUtils.dp2px(context, 3);
imageViewClose.setLayoutParams(layoutParams);
toucherLayout.addView(imageViewClose,layoutParams);
//添加toucherlayout
if(isInit) {
windowManager.addView(toucherLayout,params);
} else {
windowManager.updateViewLayout(toucherLayout,params);
}
//主动计算出当前View的宽高信息.
toucherLayout.measure(View.MeasureSpec.UNSPECIFIED,View.MeasureSpec.UNSPECIFIED);
//处理touch
toucherLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isMoved = false;
// 记录按下位置
lastX = event.getRawX();
lastY = event.getRawY();
start_X = event.getRawX();
start_Y = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
isMoved = true;
// 记录移动后的位置
float moveX = event.getRawX();
float moveY = event.getRawY();
// 获取当前窗口的布局属性, 添加偏移量, 并更新界面, 实现移动
params.x += (int) (moveX - lastX);
params.y += (int) (moveY - lastY);
windowManager.updateViewLayout(toucherLayout,params);
lastX = moveX;
lastY = moveY;
break;
case MotionEvent.ACTION_UP:
float fmoveX = event.getRawX();
float fmoveY = event.getRawY();
if (Math.abs(fmoveX-start_X)<offset && Math.abs(fmoveY-start_Y)<offset){
isMoved = false;
remove(context);
leaveCast(context);
String PARAM_CIRCLE_ID = "param_circle_id";
Intent intent = new Intent();
intent.putExtra(PARAM_CIRCLE_ID,circle_id);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(RePlugin.getHostContext().getPackageName(),
"com.sina.licaishicircle.sections.circledetail.CircleActivity"));
context.startActivity(intent);
}else {
isMoved = true;
}
break;
}
// 如果是移动事件, 则消费掉; 如果不是, 则由其他处理, 比如点击
return isMoved;
}
});
//删除
imageViewClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
remove(context);
leaveCast(context);
}
});
}catch (Exception e){
e.printStackTrace();
}
isInit = false;
}
public void remove(Context context) {
if(windowManager != null && toucherLayout != null) {
windowManager.removeView(toucherLayout);
}
}
/**
* 获取屏幕宽度(px)
*/
public int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
/**
* 获取屏幕高度(px)
*/
public int getScreenHeight(Context context){
return context.getResources().getDisplayMetrics().heightPixels;
}
/**
* 退出的时候请调用
*/
public void leaveCast(Context context) {
if (null != mPlayer&& null!=context) {
mPlayer.leave();
mPlayer.release(context);
//直播资源销毁需要重新初始化
isInit = true;
}
}
}
反射部分代码
/**
* Description:
* Created by PangHaHa on 18-7-23.
* Copyright (c) 2018 PangHaHa All rights reserved.
*/
public class LiveWindowUtil {
private static class Hold {
public static LiveWindowUtil instance = new LiveWindowUtil();
}
public static LiveWindowUtil getInstance() {
return Hold.instance;
}
public LiveWindowUtil() {
//代码使用插件Fragment
RePlugin.fetchContext("sina.com.cn.courseplugin");
}
private Object o;
private Class clazz;
public void init(Context context, Map map) {
try {
ClassLoader classLoader = RePlugin.fetchClassLoader("sina.com.cn.courseplugin");//获取插件的ClassLoader
clazz = classLoader.loadClass("sina.com.cn.courseplugin.tools.LiveUtils");
o = clazz.newInstance();
Method method = clazz.getMethod("initLive", Context.class, Map.class);
method.invoke(o, context, map);
}catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}catch (NullPointerException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public void remove(Context context) {
Method method = null;
try {
if(clazz != null && o != null) {
method = clazz.getMethod("remove", Context.class);
method.invoke(o,context);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}