国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

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

2019-12-12 01:37:28
字體:
供稿:網(wǎng)友

大家在使用主流的視頻軟件以及直播軟件的時候,經(jīng)常會看到打開視頻最小化以后,不是直接關(guān)閉,而是在屏幕右下角一個小窗口的樣子,本次小編就給大家?guī)淼氖怯肁ndroid實現(xiàn)在視頻或者語音通話的時候,最小化也是出現(xiàn)一個懸浮框的效果。

關(guān)于音視頻通話過程中最小化成懸浮框這個功能的實現(xiàn),網(wǎng)絡(luò)上類似的文章很多,但是好像還沒看到解釋的較為清晰的,這里因為項目需要實現(xiàn)了這樣的一個功能,今天我把它記錄下來,一方面為了以后用到便于自己查閱,一方面也給有需要的人提供一個思路,讓大家少走彎路。這里我也是參考了些有關(guān)Android懸浮框的文章,再結(jié)合自己的理解所實現(xiàn)出來的,可能實現(xiàn)的方法不是最好,但是這或許也是一個可行的方案。

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

二、實現(xiàn)思路

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

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

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

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

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

2.懸浮框是如何開啟的?

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

a. 首先我們聲明一個服務(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. 為懸浮框建立一個布局文件alert_float_video_layout,這里根據(jù)需求去寫,如果只是像我上面gif那樣,只需要懸浮框顯示對方的視頻畫布,那么布局文件可以如下所示:(其中懸浮框大小我這里固定為長80dp,高110dp,id為small_size_preview的Linearlayout主要是一個容器,可以動態(tài)的添加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. 布局定義好后,接下來就要對懸浮框做一些初始化操作了,初始化操作這里我們放在服務(wù)的onCreate()生命周期里面執(zhí)行,因為只需要執(zhí)行一次就行了。這里的初始化主要包括對:懸浮框的基本參數(shù)(位置,寬高等),懸浮框的點擊事件以及懸浮框的觸摸事件(即可拖動范圍)等的設(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; //浮動布局 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();//懸浮框點擊事件的處理 } @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;  //懸浮窗的開始位置,因為設(shè)置的是從左上角開始,所以屏幕左上角是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();  //設(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è)置懸浮窗口長寬數(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);  //懸浮框點擊事件  smallSizePreviewLayout.setOnClickListener(new View.OnClickListener() {   @Override   public void onClick(View v) {     //在這里實現(xiàn)點擊重新回到Activity   }  });  //懸浮框觸摸事件,設(shè)置懸浮框可拖動  smallSizePreviewLayout.setOnTouchListener(new FloatingListener()); } //開始觸控的坐標(biāo),移動時的坐標(biāo)(相對于屏幕左上角的坐標(biāo)) private int mTouchStartX, mTouchStartY, mTouchCurrentX, mTouchCurrentY; //開始時的坐標(biāo)和結(jié)束時的坐標(biāo)(相對于自身控件的坐標(biāo)) private int mStartX, mStartY, mStopX, mStopY;   //判斷懸浮窗口是否移動,這里做個標(biāo)記,防止移動后松手觸發(fā)了點擊事件 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;   }   //如果是移動事件不觸發(fā)OnClick事件,防止移動的時候一放手形成點擊事件   return isMove;  } }}

d. 在懸浮框成功被初始化以及相關(guān)參數(shù)被設(shè)置后,接下來就需要將對方的視頻畫布添加到懸浮框里面去了,這樣我們才能看到對方的視頻畫面嘛,同樣我們是在Service的oncreate這個生命周期完成這個操作的,這里視頻畫布的添加方式使用的網(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. 我們上面說到要將服務(wù)service的綁定與解綁與懸浮框的開啟和關(guān)閉相結(jié)合,所以既然我們在服務(wù)的oncreate()方法中開啟了懸浮框,那么就應(yīng)該在其ondestroy()方法中對懸浮框進行關(guān)閉,關(guān)閉懸浮框的本質(zhì)是將相關(guān)view給移除掉,接著清除我們的視頻畫布,在服務(wù)的ondestroy()方法中執(zhí)行如下代碼:

@Override public void onDestroy() {  super.onDestroy();  if (mFloatingLayout != null) {   // 移除懸浮窗口   mWindowManager.removeView(mFloatingLayout);  }  //清除視頻畫布  AVChatManager.getInstance().setupRemoteVideoRender(account, null, false, 0); }

f. 服務(wù)的綁定方式有bindService和startService兩種,使用不同的綁定方式其生命周期也會不一樣,已知我們需要讓懸浮框在視頻通話activity finish掉的時候也順便關(guān)掉,那么理所當(dāng)然我們就應(yīng)該采用bind方式來啟動服務(wù),讓他的生命周期跟隨他的開啟者,也即是跟隨開啟它的activity生命周期。

intent = new Intent(this, FloatVideoWindowService.class);//開啟服務(wù)顯示懸浮框bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE);ServiceConnection mVideoServiceConnection = new ServiceConnection() {  @Override  public void onServiceConnected(ComponentName name, IBinder service) {   // 獲取服務(wù)的操作對象   FloatVideoWindowService.MyBinder binder = (FloatVideoWindowService.MyBinder) service;   binder.getService();  }  @Override  public void onServiceDisconnected(ComponentName name) {  } };

三、完整的流程

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

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

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

@Override protected void onRestart() {  super.onRestart();  unbindService(mVideoServiceConnection);//不顯示懸浮框  //從懸浮窗進來后重新設(shè)置畫布(判斷是不是接通了)  if (isCallEstablished) {   //如果接通,先清除所有畫布   avChatUI.clearAllSurfaceView(avChatUI.getAccount());   //延遲重新加載遠端和本地的視頻畫布   mHandler.postDelayed(new Runnable() {    @Override    public void run() {     avChatUI.initAllSurfaceView(avChatUI.getAccount());         }   }, 800);  } else {   //如果沒接通,直接初始化所有畫布   avChatUI.initLargeSurfaceView(IMCache.getAccount());  } }

以上就是本次為大家分享的關(guān)于Android開發(fā)的又一功能實現(xiàn)方式,希望我們整理的能夠幫助到你。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 武川县| 巴彦淖尔市| 隆林| 博客| 贵溪市| 宝应县| 雷山县| 德昌县| 广西| 松潘县| 准格尔旗| 周口市| 洛扎县| 客服| 古浪县| 康平县| 乡宁县| 宁安市| 辽源市| 肇源县| 财经| 庄河市| 太白县| 石阡县| 蕉岭县| 四川省| 宁阳县| 保德县| 陆丰市| 昭通市| 如皋市| 富源县| 清水河县| 团风县| 华池县| 清镇市| 嘉兴市| 黔南| 长武县| 吉水县| 家居|