Android音視訊通話過程中最小化成懸浮框的實現(類似Android8.0畫中畫效果)

來源:互聯網
上載者:User

標籤:new   視訊通話   false   roo   ret   param   添加   setup   stop   

關於音視訊通話過程中最小化成懸浮框這個功能的實現,網路上類似的文章很多,但是好像還沒看到解釋的較為清晰的,這裡因為項目需要實現了這樣的一個功能,今天我把它記錄下來,一方面為了以後用到便於自己查閱,一方面也給有需要的人提供一個思路,讓大家少走彎路。這裡我也是參考了些有關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());        }    }                        

 

已經很久沒有寫過部落格了,寫著寫著可能有點亂( ̄_ ̄|||)

如果有什麼疑問或者有更好的實現思路的,歡迎給我留言~

連絡方式:[email protected]

Android音視訊通話過程中最小化成懸浮框的實現(類似Android8.0畫中畫效果)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.