直播小弹窗,WindowManager 实现悬浮窗 详解

一:对于想直接看效果的,可以看看我的demo app.

链接:http://sj.qq.com/myapp/detail.htm?apkName=com.inno.backdot

源码:https://github.com/didikee/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();
        }

    }
}

猜你喜欢

转载自blog.csdn.net/panghaha12138/article/details/81208108