欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android仿微信视屏悬浮窗效果

程序员文章站 2023-10-31 08:33:40
在项目中需要对接入的腾讯云音视频,可以悬浮窗显示,悬浮窗可拖拽,并且在悬浮窗不影响其他的activity的焦点。 这个大神的文章android基于腾讯云实时音视频仿微信视频通话最小化...

在项目中需要对接入的腾讯云音视频,可以悬浮窗显示,悬浮窗可拖拽,并且在悬浮窗不影响其他的activity的焦点。

这个大神的文章android基于腾讯云实时音视频仿微信视频通话最小化悬浮,他讲的是视频通话时,将远端视频以悬浮窗形式展示,根据他的代码我进行了部分简化

1.悬浮窗效果:点击缩小按钮,将当前远端视屏加载进悬浮窗,且悬浮窗可拖拽,不影响其他界面焦点;点击悬浮窗可返回原来的activity

2.实现悬浮窗需要:

在androidmanifest中申请悬浮窗权限<uses-permission android:name="android.permission.system_alert_window"/>

在androidmanifest中注册floatwindowservice

3.视屏activity实现:

-将activity置于后台关键代码:movetasktoback(true);//将activity置于后台
-开启悬浮窗

/**
   * 定义服务绑定的回调 开启视频通话服务连接
   */
  private serviceconnection mvideocallserviceconnection = new serviceconnection() {

    @override
    public void onserviceconnected(componentname name, ibinder service) {
      // 获取服务的操作对象
      floatwindowservice.mybinder binder = (floatwindowservice.mybinder) service;
      binder.getservice();
    }

    @override
    public void onservicedisconnected(componentname name) {

    }
  };

/*
   * 开启悬浮video服务
   */
  private void startvideoservice() {
    //最小化activity
    movetasktoback(true);//将activity置于后台
    //开启服务显示悬浮框
    intent servicevideointent = new intent(this, floatwindowservice.class);
    mservicebound = bindservice(servicevideointent, mvideocallserviceconnection, context.bind_auto_create);//绑定service
  }

-悬浮窗结束时

//在ondestroy()与onrestart()中解绑并销毁相关内容
if (mservicebound) {
      unbindservice(mvideocallserviceconnection);//解绑
      mservicebound = false;
    }

4.悬浮窗实现相关代码: 

/**
 * 视频悬浮窗服务
 */
public class floatwindowservice extends service implements view.ontouchlistener {
  private windowmanager mwindowmanager;
  private windowmanager.layoutparams wmparams;
  private layoutinflater inflater;
  //浮动布局view
  private view mfloatinglayout;
  //容器父布局
  private view mmainview;
 
  //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
  private int mtouchstartx, mtouchstarty, mtouchcurrentx, mtouchcurrenty;
  //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
  private int mstartx, mstarty, mstopx, mstopy;
  //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
  private boolean ismove;
 
 
  @override
  public void oncreate() {
    super.oncreate();
    initwindow();//设置悬浮窗基本参数(位置、宽高等)
 
  }
 
  @nullable
  @override
  public ibinder onbind(intent intent) {
    currentbiguserid = intent.getstringextra("localuserid");
    remoteuserid = intent.getstringextra("remoteuserid");
    initfloating();//悬浮框点击事件的处理
    return new mybinder();
  }
 
  public class mybinder extends binder {
    public floatwindowservice getservice() {
      return floatwindowservice.this;
    }
  }
 
 
  @override
  public int onstartcommand(intent intent, int flags, int startid) {
    return super.onstartcommand(intent, flags, startid);
  }
 
  @override
  public void ondestroy() {
    super.ondestroy();
    if (mfloatinglayout != null) {
      // 移除悬浮窗口
      mwindowmanager.removeview(mfloatinglayout);
      mfloatinglayout = null;
    }
  }
 
  /**
   * 设置悬浮框基本参数(位置、宽高等)
   */
  private void initwindow() {
    mwindowmanager = (windowmanager) getapplicationcontext().getsystemservice(context.window_service);
    //设置好悬浮窗的参数
    wmparams = getparams();
    // 悬浮窗默认显示以左上角为起始坐标
    wmparams.gravity = gravity.right | gravity.top;
    //悬浮窗的开始位置,因为设置的是从右上角开始,所以屏幕左上角是x=屏幕最大值;y=0
    wmparams.x = 10;
    wmparams.y = 120;
    //得到容器,通过这个inflater来获得悬浮窗控件
    inflater = layoutinflater.from(getapplicationcontext());
    // 获取浮动窗口视图所在布局
    mfloatinglayout = inflater.inflate(r.layout.dlg_floatview, null);
    // 添加悬浮窗的视图
    mwindowmanager.addview(mfloatinglayout, wmparams);
  }
 
 
  private windowmanager.layoutparams getparams() {
    wmparams = new windowmanager.layoutparams();
    if (build.version.sdk_int >= build.version_codes.o) {
      wmparams.type = windowmanager.layoutparams.type_application_overlay;
    } else {
      wmparams.type = windowmanager.layoutparams.type_phone;
    }
    //设置可以显示在状态栏上
    wmparams.flags = windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_not_touch_modal |
        windowmanager.layoutparams.flag_layout_in_screen | windowmanager.layoutparams.flag_layout_inset_decor |
        windowmanager.layoutparams.flag_watch_outside_touch;
 
    //设置悬浮窗口长宽数据
    wmparams.width = windowmanager.layoutparams.wrap_content;
    wmparams.height = windowmanager.layoutparams.wrap_content;
    return wmparams;
  }
 
  //加载远端视屏:在这对悬浮窗内内容做操作
  private void initfloating() {
    //将子view加载进悬浮窗view
    mmainview = mfloatinglayout.findviewbyid(r.id.trtc_video_view_layout_float);//悬浮窗父布局
    view mchildview = renderview.getchildview();//加载进悬浮窗的子view,这个view来自天转过来的那个activity里面的那个需要加载的view
    mmainview.addview(mchildview);//将需要悬浮显示的viewadd到mtxcloudvideoview中
    
    //悬浮框触摸事件,设置悬浮框可拖动
    mtxcloudvideoview.setontouchlistener(this::ontouch);
    //悬浮框点击事件
    mtxcloudvideoview.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        //在这里实现点击重新回到activity
        intent intent = 
        new intent(floatwindowservice.this, rtcactivity.class);//从该service跳转至该activity会将该activity从后台唤醒,所以activity会走onrestart()
        intent.setflags(intent.flag_activity_new_task);//从service跳转至rtcactivity,需要intent.flag_activity_new_task,不然会崩溃
        startactivity(intent);
      }
    });
 
  }
 
  //触摸事件
  @override
  public boolean ontouch(view v, motionevent event) {
    int action = event.getaction();
    switch (action) {
      case motionevent.action_down:
        ismove = false;
        mtouchstartx = (int) event.getrawx();
        mtouchstarty = (int) event.getrawy();
        mstartx = (int) event.getx();
        mstarty = (int) event.gety();
        break;
      case motionevent.action_move:
        mtouchcurrentx = (int) event.getrawx();
        mtouchcurrenty = (int) event.getrawy();
        wmparams.x += mtouchstartx - mtouchcurrentx;
        wmparams.y += mtouchcurrenty - mtouchstarty;
        alog.dtag("floatinglistener() ontouch",mtouchcurrentx,mtouchstartx,mtouchcurrenty,mtouchstarty);
        mwindowmanager.updateviewlayout(mfloatinglayout, wmparams);
 
        mtouchstartx = mtouchcurrentx;
        mtouchstarty = mtouchcurrenty;
        break;
      case motionevent.action_up:
        mstopx = (int) event.getx();
        mstopy = (int) event.gety();
        if (math.abs(mstartx - mstopx) >= 1 || math.abs(mstarty - mstopy) >= 1) {
          ismove = true;
        }
        break;
      default:
        break;
    }
    //如果是移动事件不触发onclick事件,防止移动的时候一放手形成点击事件
    return ismove;
  }
 
}

ps:使用service做悬浮窗的载体是为了,将悬浮框的开启关闭与服务service的绑定解绑所关联起来,开启服务即相当于开启我们的悬浮框,解绑服务则相当于关闭悬浮框,以此来达到更好的控制效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。