本文介紹如何在Android端實現畫中畫(懸浮窗)。
功能介紹
“畫中畫”(Picture-in-Picture,簡稱 PiP)是一種讓視頻“懸浮”在螢幕上的功能。開啟後,視頻會以一個小視窗的形式顯示在螢幕一角,使用者可以一邊看視頻,一邊正常使用手機或電腦的其他應用,比如回訊息、瀏覽網頁或操作其他App,互不干擾。
範例程式碼
ARTC 提供了開源範例程式碼供您參考:Android實現畫中畫。
前提條件
要實現畫中畫功能,需要滿足如下條件:
Android 版本不能低於 Android 8.0(API 層級 26)。
功能實現
1. 配置相關屬性
畫中畫的 API 維度為 Activity,因此需要在 AndroidManifest.xml 中為 Activity 聲明下列屬性
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
<activity android:name=".PictureInPicture.PictureInPictureAcitivity"
android:label="@string/picture_in_picture"
android:exported="false"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:resizeableActivity="true"
android:launchMode="singleTask"
tools:targetApi="24" />2. 進入畫中畫模式
部分裝置可能無法使用畫中畫模式,因此進入畫中畫模式前需要調用
hasSystemFeature介面進行檢查。調用系統 API
enterPictureInPictureMode進入畫中畫模式。
private void enterPIPMode(){
if(!hasJoined) {
ToastHelper.showToast(this, "請先加入頻道", Toast.LENGTH_SHORT);
return;
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
Rational aspectRatio = new Rational(9, 16); // 推薦視頻比例
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build();
enterPictureInPictureMode(params);
} else {
ToastHelper.showToast(this, "裝置不支援畫中畫", Toast.LENGTH_SHORT);
}
} else {
ToastHelper.showToast(this, "Android 8.0 以上才支援畫中畫", Toast.LENGTH_SHORT);
}
}3. 響應畫中畫變化
可以重寫系統onPictureInPictureModeChanged回調並根據業務情境控制 UI 變化。
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (isInPictureInPictureMode) {
// 進入 PIP:隱藏 全屏UI,只保留視頻
findViewById(R.id.ll_channel_layout).setVisibility(View.GONE);
findViewById(R.id.scroll_remote_container).setVisibility(View.GONE);
// 可選:隱藏狀態列
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
// 可選:建立專用 PIP 視頻容器
showPipVideoContainer();
} else {
// 退出 PIP:恢複 全屏UI
findViewById(R.id.ll_channel_layout).setVisibility(View.VISIBLE);
findViewById(R.id.scroll_remote_container).setVisibility(View.VISIBLE);
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
// 重新綁定本地預覽視圖
if (mAliRtcEngine != null && mLocalVideoCanvas != null) {
mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
}
// 移除 PIP 容器
removePipVideoContainer();
}
}
/**
* 顯示 PIP 模式下的主視頻容器(例如本地預覽)
*/
private void showPipVideoContainer() {
if (mPipVideoContainer == null) {
mPipVideoContainer = new FrameLayout(this);
mPipVideoContainer.setBackgroundColor(Color.BLACK);
addContentView(mPipVideoContainer, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// 重新建立本地預覽視圖(不能移動 SurfaceView,只能重建)
SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(this);
surfaceView.setZOrderOnTop(true);
surfaceView.setZOrderMediaOverlay(true);
mPipVideoContainer.addView(surfaceView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if(mAliRtcEngine != null) {
AliRtcEngine.AliRtcVideoCanvas canvas = new AliRtcEngine.AliRtcVideoCanvas();
canvas.view = surfaceView;
mAliRtcEngine.setLocalViewConfig(canvas, AliRtcVideoTrackCamera);
}
}
}
/**
* 移除 PIP 視頻容器
*/
private void removePipVideoContainer() {
if (mPipVideoContainer != null && mPipVideoContainer.getParent() != null) {
((ViewGroup) mPipVideoContainer.getParent()).removeView(mPipVideoContainer);
mPipVideoContainer = null;
}
}