当前位置: 移动技术网 > IT编程>移动开发>Android > Android实现通话最小化悬浮框效果

Android实现通话最小化悬浮框效果

2019年07月24日  | 移动技术网IT编程  | 我要评论

煤油的密度,高铨资料,嘉鱼县公安局

大家在使用主流的视频软件以及直播软件的时候,经常会看到打开视频最小化以后,不是直接关闭,而是在屏幕右下角一个小窗口的样子,本次小编就给大家带来的是用android实现在视频或者语音通话的时候,最小化也是出现一个悬浮框的效果。

关于音视频通话过程中最小化成悬浮框这个功能的实现,网络上类似的文章很多,但是好像还没看到解释的较为清晰的,这里因为项目需要实现了这样的一个功能,今天我把它记录下来,一方面为了以后用到便于自己查阅,一方面也给有需要的人提供一个思路,让大家少走弯路。这里我也是参考了些有关android悬浮框的文章,再结合自己的理解所实现出来的,可能实现的方法不是最好,但是这或许也是一个可行的方案。

一、实现效果(gif效果可能录制的不是特别好)

二、实现思路

关于这个功能的实现其实不难,这里我把实现思路拆分为了两步:1、视频通话activity的最小化。 2、视频通话悬浮框的开启

具体思路是这样的:当用户点击最小化按钮的时候,最小化我们的视频通话activity(这时activity处于后台状态),移除原先在activity的视频画布(因为我用的是网易云信,这里他们只能允许一个视频画布存在,这里看情况要不要移除),于此同时,延时个几百毫秒,开启悬浮框,新建一个新的视频画布然后动态添加到悬浮框里面去,监听悬浮框的触摸事件,让悬浮框可以拖拽移动;监听悬浮框的点击事件,如果用户点击了悬浮框,则移除悬浮框里面新建的那个视频画布,然后重新调起我们在后台的视频通话activity,紧接着新建一个新的视频画布重新动态的添加到activity里面去。关于视频画布的添加移除方法,这里要看一下所接入的第三方sdk,如用的若是网易云信的sdk,他们的方法如下(下面摘自他们的sdk说明文档),也就是说移除画布我只需要传入null就行了。

1.activity是如何实现最小化的?

activity最小化可能你没有听过,但是只要姿势对的话,其实实现起来非常简单,因为activity本身就自带了一个movetasktoback(boolean nonroot),如果我们要实现最小化,只需要调用movetasktoback(true)传入一个true值就可以了,但是这里有一个前提,就是需要设置activity的启动模式为singleinstance模式,两步搞定。(注:这里先记住一个小知识点,就是activity最小化后重新从后台回到前台会回调onrestart()方法)

@override
public boolean movetasktoback(boolean nonroot) {
return super.movetasktoback(nonroot);
}

2.悬浮框是如何开启的?

这里我把悬浮框的实现方法写在一个服务service里面,将悬浮框的开启关闭与服务service的绑定解绑所关联起来,开启服务即相当于开启我们的悬浮框,解绑服务则相当于关闭关闭的悬浮框,以此来达到更好的控制效果。

a. 首先我们声明一个服务类,取名为floatvideowindowservice:

public class floatvideowindowservice extends service {

 @nullable
 @override
 public ibinder onbind(intent intent) {
  return new mybinder();
 }

 public class mybinder extends binder {
  public floatvideowindowservice getservice() {
   return floatvideowindowservice.this;
  }
 }

 @override
 public void oncreate() {
  super.oncreate();
 }

 @override
 public int onstartcommand(intent intent, int flags, int startid) {
  return super.onstartcommand(intent, flags, startid);
 }

 @override
 public void ondestroy() {
  super.ondestroy();
 }
}

b. 为悬浮框建立一个布局文件alert_float_video_layout,这里根据需求去写,如果只是像我上面gif那样,只需要悬浮框显示对方的视频画布,那么布局文件可以如下所示:(其中悬浮框大小我这里固定为长80dp,高110dp,id为small_size_preview的linearlayout主要是一个容器,可以动态的添加view到里面去,也就是我们的视频画布)

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content">

 <framelayout
  android:layout_width="80dp"
  android:layout_height="110dp"
  android:background="@color/black_1f2d3d">

  <linearlayout
   android:id="@+id/small_size_preview"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@color/transparent"
   android:orientation="vertical" />
 </framelayout>
</linearlayout>

c. 布局定义好后,接下来就要对悬浮框做一些初始化操作了,初始化操作这里我们放在服务的oncreate()生命周期里面执行,因为只需要执行一次就行了。这里的初始化主要包括对:悬浮框的基本参数(位置,宽高等),悬浮框的点击事件以及悬浮框的触摸事件(即可拖动范围)等的设置,代码注释已经很清楚,直接看代码,如下所示:

public class floatvideowindowservice extends service {
 private windowmanager mwindowmanager;
 private windowmanager.layoutparams wmparams;
 private layoutinflater inflater;

 //constant
 private boolean clickflag;

 //view
 private view mfloatinglayout; //浮动布局
 private linearlayout smallsizepreviewlayout; //容器父布局

 @nullable
 @override
 public ibinder onbind(intent intent) {
  return new mybinder();
 }

 public class mybinder extends binder {
  public floatvideowindowservice getservice() {
   return floatvideowindowservice.this;
  }
 }

 @override
 public void oncreate() {
  super.oncreate();
  initwindow();//设置悬浮窗基本参数(位置、宽高等)
  initfloating();//悬浮框点击事件的处理
 }

 @override
 public int onstartcommand(intent intent, int flags, int startid) {
  return super.onstartcommand(intent, flags, startid);
 }

 
 @override
 public void ondestroy() {
  super.ondestroy();
 }

 /**
  * 设置悬浮框基本参数(位置、宽高等)
  */
 private void initwindow() {
  mwindowmanager = (windowmanager) getapplicationcontext().getsystemservice(context.window_service);
  wmparams = getparams();//设置好悬浮窗的参数
  // 悬浮窗默认显示以左上角为起始坐标
  wmparams.gravity = gravity.left | gravity.top;
  //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
  wmparams.x = 70;
  wmparams.y = 210;
  //得到容器,通过这个inflater来获得悬浮窗控件
  inflater = layoutinflater.from(getapplicationcontext());
  // 获取浮动窗口视图所在布局
  mfloatinglayout = inflater.inflate(r.layout.alert_float_video_layout, null);
  // 添加悬浮窗的视图
  mwindowmanager.addview(mfloatinglayout, wmparams);
 }

 
 private windowmanager.layoutparams getparams() {
  wmparams = new windowmanager.layoutparams();
  //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
  wmparams.type = windowmanager.layoutparams.type_toast;
  //设置可以显示在状态栏上
  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() {
  smallsizepreviewlayout = mfloatinglayout.findviewbyid(r.id.small_size_preview);

  //悬浮框点击事件
  smallsizepreviewlayout.setonclicklistener(new view.onclicklistener() {
   @override
   public void onclick(view v) {
     //在这里实现点击重新回到activity
   }
  });

  //悬浮框触摸事件,设置悬浮框可拖动
  smallsizepreviewlayout.setontouchlistener(new floatinglistener());
 }

 //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
 private int mtouchstartx, mtouchstarty, mtouchcurrentx, mtouchcurrenty;
 //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
 private int mstartx, mstarty, mstopx, mstopy;
   //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
 private boolean ismove;

 private class floatinglistener implements view.ontouchlistener {

  @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 += mtouchcurrentx - mtouchstartx;
     wmparams.y += 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;
   }

   //如果是移动事件不触发onclick事件,防止移动的时候一放手形成点击事件
   return ismove;
  }
 }
}

d. 在悬浮框成功被初始化以及相关参数被设置后,接下来就需要将对方的视频画布添加到悬浮框里面去了,这样我们才能看到对方的视频画面嘛,同样我们是在service的oncreate这个生命周期完成这个操作的,这里视频画布的添加方式使用的网易云信的sdk,具体的添加方式视不同的sdk而定,代码如下所示:

/**
  * 初始化预览窗口
  */
 private void initsurface() {
  if (smallrender == null) {
   smallrender = new avchatsurfaceviewrenderer(getapplicationcontext());
  }

  addintosmallsizepreviewlayout(smallrender);
 }

 /**
  * 添加surfaceview到smallsizepreviewlayout
  */
 private void addintosmallsizepreviewlayout(surfaceview surfaceview) {
  if (surfaceview.getparent() != null) {
   ((viewgroup) surfaceview.getparent()).removeview(surfaceview);
  }

  smallsizepreviewlayout.addview(surfaceview);
  surfaceview.setzordermediaoverlay(true);
 }

e. 我们上面说到要将服务service的绑定与解绑与悬浮框的开启和关闭相结合,所以既然我们在服务的oncreate()方法中开启了悬浮框,那么就应该在其ondestroy()方法中对悬浮框进行关闭,关闭悬浮框的本质是将相关view给移除掉,接着清除我们的视频画布,在服务的ondestroy()方法中执行如下代码:

@override
 public void ondestroy() {
  super.ondestroy();
  if (mfloatinglayout != null) {
   // 移除悬浮窗口
   mwindowmanager.removeview(mfloatinglayout);
  }

  //清除视频画布
  avchatmanager.getinstance().setupremotevideorender(account, null, false, 0);
 }

f. 服务的绑定方式有bindservice和startservice两种,使用不同的绑定方式其生命周期也会不一样,已知我们需要让悬浮框在视频通话activity finish掉的时候也顺便关掉,那么理所当然我们就应该采用bind方式来启动服务,让他的生命周期跟随他的开启者,也即是跟随开启它的activity生命周期。

intent = new intent(this, floatvideowindowservice.class);//开启服务显示悬浮框
bindservice(intent, mvideoserviceconnection, context.bind_auto_create);

serviceconnection mvideoserviceconnection = new serviceconnection() {

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

  @override
  public void onservicedisconnected(componentname name) {
  }
 };

三、完整的流程

现在我们将上面所说的给串联起来,思路会更加清晰一点,假设现在我正在进行视频通话,点击视频最小化按钮,我们应该按顺序执行如下步骤:(如果你姿势对的话,现在应该是会出现个悬浮框了)

public void startvideoservice() {
   movetasktoback(true);//最小化activity
   intent = new intent(this, floatvideowindowservice.class);//开启服务显示悬浮框
   bindservice(intent, mvideoserviceconnection, context.bind_auto_create);
 }

当我们点击悬浮框的时候,可以使用startactivity(intent)来再次打开我们的activity,这时候视频通话activity会回调onrestart()方法,我们在onrestart()生命周期里面unbind解绑掉悬浮框服务,并且重新设置新的视频画布到activity上

@override
 protected void onrestart() {
  super.onrestart();
  unbindservice(mvideoserviceconnection);//不显示悬浮框

  //从悬浮窗进来后重新设置画布(判断是不是接通了)
  if (iscallestablished) {
   //如果接通,先清除所有画布
   avchatui.clearallsurfaceview(avchatui.getaccount());
   //延迟重新加载远端和本地的视频画布
   mhandler.postdelayed(new runnable() {
    @override
    public void run() {
     avchatui.initallsurfaceview(avchatui.getaccount());
     
    }
   }, 800);
  } else {
   //如果没接通,直接初始化所有画布
   avchatui.initlargesurfaceview(imcache.getaccount());
  }
 }

以上就是本次为大家分享的关于android开发的又一功能实现方式,希望我们整理的能够帮助到你。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网