溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android如何實(shí)現(xiàn)通話最小化懸浮框效果

發(fā)布時(shí)間:2021-04-16 13:40:11 來(lái)源:億速云 閱讀:304 作者:小新 欄目:移動(dòng)開(kāi)發(fā)

這篇文章主要介紹Android如何實(shí)現(xiàn)通話最小化懸浮框效果,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

一、實(shí)現(xiàn)效果(gif效果可能錄制的不是特別好)

Android如何實(shí)現(xiàn)通話最小化懸浮框效果

Android如何實(shí)現(xiàn)通話最小化懸浮框效果

二、實(shí)現(xiàn)思路

關(guān)于這個(gè)功能的實(shí)現(xiàn)其實(shí)不難,這里我把實(shí)現(xiàn)思路拆分為了兩步:1、視頻通話Activity的最小化。 2、視頻通話懸浮框的開(kāi)啟

具體思路是這樣的:當(dāng)用戶點(diǎn)擊最小化按鈕的時(shí)候,最小化我們的視頻通話Activity(這時(shí)Activity處于后臺(tái)狀態(tài)),移除原先在Activity的視頻畫(huà)布(因?yàn)槲矣玫氖蔷W(wǎng)易云信,這里他們只能允許一個(gè)視頻畫(huà)布存在,這里看情況要不要移除),于此同時(shí),延時(shí)個(gè)幾百毫秒,開(kāi)啟懸浮框,新建一個(gè)新的視頻畫(huà)布然后動(dòng)態(tài)添加到懸浮框里面去,監(jiān)聽(tīng)?wèi)腋】虻挠|摸事件,讓?xiě)腋】蚩梢酝献б苿?dòng);監(jiān)聽(tīng)?wèi)腋】虻狞c(diǎn)擊事件,如果用戶點(diǎn)擊了懸浮框,則移除懸浮框里面新建的那個(gè)視頻畫(huà)布,然后重新調(diào)起我們?cè)诤笈_(tái)的視頻通話Activity,緊接著新建一個(gè)新的視頻畫(huà)布重新動(dòng)態(tài)的添加到Activity里面去。關(guān)于視頻畫(huà)布的添加移除方法,這里要看一下所接入的第三方SDK,如用的若是網(wǎng)易云信的SDK,他們的方法如下(下面摘自他們的SDK說(shuō)明文檔),也就是說(shuō)移除畫(huà)布我只需要傳入null就行了。

Android如何實(shí)現(xiàn)通話最小化懸浮框效果

1.Activity是如何實(shí)現(xiàn)最小化的?

Activity最小化可能你沒(méi)有聽(tīng)過(guò),但是只要姿勢(shì)對(duì)的話,其實(shí)實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,因?yàn)锳ctivity本身就自帶了一個(gè)moveTaskToBack(boolean nonRoot),如果我們要實(shí)現(xiàn)最小化,只需要調(diào)用moveTaskToBack(true)傳入一個(gè)true值就可以了,但是這里有一個(gè)前提,就是需要設(shè)置Activity的啟動(dòng)模式為singleInstance模式,兩步搞定。(注:這里先記住一個(gè)小知識(shí)點(diǎn),就是activity最小化后重新從后臺(tái)回到前臺(tái)會(huì)回調(diào)onRestart()方法)

@Override
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}

2.懸浮框是如何開(kāi)啟的?

這里我把懸浮框的實(shí)現(xiàn)方法寫(xiě)在一個(gè)服務(wù)Service里面,將懸浮框的開(kāi)啟關(guān)閉與服務(wù)Service的綁定解綁所關(guān)聯(lián)起來(lái),開(kāi)啟服務(wù)即相當(dāng)于開(kāi)啟我們的懸浮框,解綁服務(wù)則相當(dāng)于關(guān)閉關(guān)閉的懸浮框,以此來(lái)達(dá)到更好的控制效果。

a. 首先我們聲明一個(gè)服務(wù)類,取名為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. 為懸浮框建立一個(gè)布局文件alert_float_video_layout,這里根據(jù)需求去寫(xiě),如果只是像我上面gif那樣,只需要懸浮框顯示對(duì)方的視頻畫(huà)布,那么布局文件可以如下所示:(其中懸浮框大小我這里固定為長(zhǎng)80dp,高110dp,id為small_size_preview的Linearlayout主要是一個(gè)容器,可以動(dòng)態(tài)的添加view到里面去,也就是我們的視頻畫(huà)布)

<?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. 布局定義好后,接下來(lái)就要對(duì)懸浮框做一些初始化操作了,初始化操作這里我們放在服務(wù)的onCreate()生命周期里面執(zhí)行,因?yàn)橹恍枰獔?zhí)行一次就行了。這里的初始化主要包括對(duì):懸浮框的基本參數(shù)(位置,寬高等),懸浮框的點(diǎn)擊事件以及懸浮框的觸摸事件(即可拖動(dòng)范圍)等的設(shè)置,代碼注釋已經(jīng)很清楚,直接看代碼,如下所示:

public class FloatVideoWindowService extends Service {
 private WindowManager mWindowManager;
 private WindowManager.LayoutParams wmParams;
 private LayoutInflater inflater;

 //constant
 private boolean clickflag;

 //view
 private View mFloatingLayout; //浮動(dòng)布局
 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();//設(shè)置懸浮窗基本參數(shù)(位置、寬高等)
  initFloating();//懸浮框點(diǎn)擊事件的處理
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  return super.onStartCommand(intent, flags, startId);
 }

 
 @Override
 public void onDestroy() {
  super.onDestroy();
 }

 /**
  * 設(shè)置懸浮框基本參數(shù)(位置、寬高等)
  */
 private void initWindow() {
  mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
  wmParams = getParams();//設(shè)置好懸浮窗的參數(shù)
  // 懸浮窗默認(rèn)顯示以左上角為起始坐標(biāo)
  wmParams.gravity = Gravity.LEFT | Gravity.TOP;
  //懸浮窗的開(kāi)始位置,因?yàn)樵O(shè)置的是從左上角開(kāi)始,所以屏幕左上角是x=0;y=0
  wmParams.x = 70;
  wmParams.y = 210;
  //得到容器,通過(guò)這個(gè)inflater來(lái)獲得懸浮窗控件
  inflater = LayoutInflater.from(getApplicationContext());
  // 獲取浮動(dòng)窗口視圖所在布局
  mFloatingLayout = inflater.inflate(R.layout.alert_float_video_layout, null);
  // 添加懸浮窗的視圖
  mWindowManager.addView(mFloatingLayout, wmParams);
 }

 
 private WindowManager.LayoutParams getParams() {
  wmParams = new WindowManager.LayoutParams();
  //設(shè)置window type 下面變量2002是在屏幕區(qū)域顯示,2003則可以顯示在狀態(tài)欄之上
  wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
  //設(shè)置可以顯示在狀態(tài)欄上
  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;

  //設(shè)置懸浮窗口長(zhǎng)寬數(shù)據(jù)
  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);

  //懸浮框點(diǎn)擊事件
  smallSizePreviewLayout.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
     //在這里實(shí)現(xiàn)點(diǎn)擊重新回到Activity
   }
  });

  //懸浮框觸摸事件,設(shè)置懸浮框可拖動(dòng)
  smallSizePreviewLayout.setOnTouchListener(new FloatingListener());
 }

 //開(kāi)始觸控的坐標(biāo),移動(dòng)時(shí)的坐標(biāo)(相對(duì)于屏幕左上角的坐標(biāo))
 private int mTouchStartX, mTouchStartY, mTouchCurrentX, mTouchCurrentY;
 //開(kāi)始時(shí)的坐標(biāo)和結(jié)束時(shí)的坐標(biāo)(相對(duì)于自身控件的坐標(biāo))
 private int mStartX, mStartY, mStopX, mStopY;
   //判斷懸浮窗口是否移動(dòng),這里做個(gè)標(biāo)記,防止移動(dòng)后松手觸發(fā)了點(diǎn)擊事件
 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;
   }

   //如果是移動(dòng)事件不觸發(fā)OnClick事件,防止移動(dòng)的時(shí)候一放手形成點(diǎn)擊事件
   return isMove;
  }
 }
}

d. 在懸浮框成功被初始化以及相關(guān)參數(shù)被設(shè)置后,接下來(lái)就需要將對(duì)方的視頻畫(huà)布添加到懸浮框里面去了,這樣我們才能看到對(duì)方的視頻畫(huà)面嘛,同樣我們是在Service的oncreate這個(gè)生命周期完成這個(gè)操作的,這里視頻畫(huà)布的添加方式使用的網(wǎng)易云信的SDK,具體的添加方式視不同的SDK而定,代碼如下所示:

/**
  * 初始化預(yù)覽窗口
  */
 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. 我們上面說(shuō)到要將服務(wù)service的綁定與解綁與懸浮框的開(kāi)啟和關(guān)閉相結(jié)合,所以既然我們?cè)诜?wù)的oncreate()方法中開(kāi)啟了懸浮框,那么就應(yīng)該在其ondestroy()方法中對(duì)懸浮框進(jìn)行關(guān)閉,關(guān)閉懸浮框的本質(zhì)是將相關(guān)view給移除掉,接著清除我們的視頻畫(huà)布,在服務(wù)的ondestroy()方法中執(zhí)行如下代碼:

@Override
 public void onDestroy() {
  super.onDestroy();
  if (mFloatingLayout != null) {
   // 移除懸浮窗口
   mWindowManager.removeView(mFloatingLayout);
  }

  //清除視頻畫(huà)布
  AVChatManager.getInstance().setupRemoteVideoRender(account, null, false, 0);
 }

f. 服務(wù)的綁定方式有bindService和startService兩種,使用不同的綁定方式其生命周期也會(huì)不一樣,已知我們需要讓?xiě)腋】蛟谝曨l通話activity finish掉的時(shí)候也順便關(guān)掉,那么理所當(dāng)然我們就應(yīng)該采用bind方式來(lái)啟動(dòng)服務(wù),讓他的生命周期跟隨他的開(kāi)啟者,也即是跟隨開(kāi)啟它的activity生命周期。

intent = new Intent(this, FloatVideoWindowService.class);//開(kāi)啟服務(wù)顯示懸浮框
bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE);

ServiceConnection mVideoServiceConnection = new ServiceConnection() {

  @Override
  public void onServiceConnected(ComponentName name, IBinder service) {
   // 獲取服務(wù)的操作對(duì)象
   FloatVideoWindowService.MyBinder binder = (FloatVideoWindowService.MyBinder) service;
   binder.getService();
  }

  @Override
  public void onServiceDisconnected(ComponentName name) {
  }
 };

三、完整的流程

現(xiàn)在我們將上面所說(shuō)的給串聯(lián)起來(lái),思路會(huì)更加清晰一點(diǎn),假設(shè)現(xiàn)在我正在進(jìn)行視頻通話,點(diǎn)擊視頻最小化按鈕,我們應(yīng)該按順序執(zhí)行如下步驟:(如果你姿勢(shì)對(duì)的話,現(xiàn)在應(yīng)該是會(huì)出現(xiàn)個(gè)懸浮框了)

public void startVideoService() {
   moveTaskToBack(true);//最小化Activity
   intent = new Intent(this, FloatVideoWindowService.class);//開(kāi)啟服務(wù)顯示懸浮框
   bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE);
 }

當(dāng)我們點(diǎn)擊懸浮框的時(shí)候,可以使用startActivity(intent)來(lái)再次打開(kāi)我們的activity,這時(shí)候視頻通話activity會(huì)回調(diào)onRestart()方法,我們?cè)趏nRestart()生命周期里面unbind解綁掉懸浮框服務(wù),并且重新設(shè)置新的視頻畫(huà)布到activity上

@Override
 protected void onRestart() {
  super.onRestart();
  unbindService(mVideoServiceConnection);//不顯示懸浮框

  //從懸浮窗進(jìn)來(lái)后重新設(shè)置畫(huà)布(判斷是不是接通了)
  if (isCallEstablished) {
   //如果接通,先清除所有畫(huà)布
   avChatUI.clearAllSurfaceView(avChatUI.getAccount());
   //延遲重新加載遠(yuǎn)端和本地的視頻畫(huà)布
   mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
     avChatUI.initAllSurfaceView(avChatUI.getAccount());
     
    }
   }, 800);
  } else {
   //如果沒(méi)接通,直接初始化所有畫(huà)布
   avChatUI.initLargeSurfaceView(IMCache.getAccount());
  }
 }

以上是“Android如何實(shí)現(xiàn)通話最小化懸浮框效果”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI