Android實現視頻的畫中畫功能
簡介: Android 8.0(API 級別 26)允許以畫中畫 (PIP) 模式啟動 Activity。畫中畫是一種特殊類型的多窗口模式,最常用於視頻播放。使用該模式,用戶可以通過固定到屏幕一角的小窗口觀看視頻,同時在應用之間進行導航或瀏覽主屏幕上的內容。
畫中畫窗口會顯示在屏幕的最上層,位於系統選擇的一角。您可以將畫中畫窗口拖動到其他位置(會自動貼邊)。當您點按該窗口時,會看到兩個特殊的控件:全屏切換開關(位於窗口的中心)和關閉按鈕(右上角的“X”)。
效果圖:
1、聲明對畫中畫的支持:
默認情況下,系統不會自動為應用提供畫中畫支持。如果您想在應用中支持畫中畫,可以通過將 android:supportsPictureInPicture 設置為 true,在清單中註冊視頻 Activity。此外,指定您的 Activity 處理佈局配置更改,這樣一來,在畫中畫模式轉換期間發生佈局更改時,您的 Activity 就不會重新啟動。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.csu.pictureinpicture"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> <activity android:name=".VideoPipActivity" android:resizeableActivity="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:supportsPictureInPicture="true" /> <activity android:name=".MediaSessionPlaybackActivity" android:resizeableActivity="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:supportsPictureInPicture="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
2、將 Activity 切換到畫中畫模式:
如要進入畫中畫模式,Activity 必須調用enterPictureInPictureMode() 。
/** * Enters Picture-in-Picture mode. */ void minimize() { if (null == mMovieView) { return; } // Hide the controls in Picture-in-Picture mode. mMovieView.hideControls(); // Calculate the aspect ratio of the PiP screen. Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight()); mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build(); enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()); }
3、處理畫中畫模式下的界面元素
當 Activity 進入或退出畫中畫模式時,系統會調用 Activity.onPictureInPictureModeChanged() 或 Fragment.onPictureInPictureModeChanged() 。在畫中畫模式下,Activity 會在一個小窗口中顯示。在畫中畫模式下,用戶無法與界面元素互動,並且可能很難看清小界面元素的詳細信息。在 Activity 進入畫中畫模式之前移除其他界面元素,並在 Activity 再次變為全屏時恢復這些元素:
@Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); if (isInPictureInPictureMode) { // Starts receiving events from action items in PiP mode. mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (null == intent || !ACTION_MEDIA_CONTROL.equals(intent.getAction())) { return; } // This is where we are called back from Picture-in-Picture action items. final int controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0); switch (controlType) { case CONTROL_TYPE_PLAY: mMovieView.play(); break; case CONTROL_TYPE_PAUSE: mMovieView.pause(); break; default: break; } } }; registerReceiver(mReceiver, new IntentFilter(ACTION_MEDIA_CONTROL)); } else { // We are out of PiP mode. We can stop receiving events from it. unregisterReceiver(mReceiver); mReceiver = null; // Show the video controls if the video is not playing if (null != mMovieView && !mMovieView.isPlaying()) { mMovieView.showControls(); } } }
完整代碼:
頁面佈局文件:
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/activity_video" android:orientation="vertical" tools:context=".VideoPipActivity"> <com.csu.pictureinpicture.widget.MovieView android:id="@+id/movie" android:layout_width="match_parent" android:layout_height="match_parent" android:adjustViewBounds="true" android:src="@raw/vid_bigbuckbunny" android:title="@string/title_bigbuckbunny"/> <ScrollView android:id="@+id/scroll" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <LinearLayout android:id="@+id/vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingEnd="@dimen/activity_horizontal_margin" android:paddingStart="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <Button android:id="@+id/pip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enter_picture_in_picture"/> </LinearLayout> </ScrollView> </LinearLayout>
Activity文件:
public class VideoPipActivity extends AppCompatActivity { /** * Intent action for media controls from Picture-in-Picture mode. */ private static final String ACTION_MEDIA_CONTROL = "media_control"; /** * Intent extra for media controls from Picture-in-Picture mode. */ private static final String EXTRA_CONTROL_TYPE = "control_type"; /** * The request code for play action PendingIntent. */ private static final int REQUEST_PLAY = 1; /** * The request code for pause action PendingIntent. */ private static final int REQUEST_PAUSE = 2; /** * The request code for info action PendingIntent. */ private static final int REQUEST_INFO = 3; /** * The intent extra value for play action. */ private static final int CONTROL_TYPE_PLAY = 1; /** * The intent extra value for pause action. */ private static final int CONTROL_TYPE_PAUSE = 2; /** * The arguments to be used for Picture-in-Picture mode. */ private final PictureInPictureParams.Builder mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder(); /** * This shows the video. */ private MovieView mMovieView; /** * The bottom half of the screen; hidden on landscape. */ private ScrollView mScrollView; /** * A {@link BroadcastReceiver} to receive action item events from Picture-in-Picture mode. */ private BroadcastReceiver mReceiver; private String mPlay; private String mPause; private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v.getId() == R.id.pip) { minimize(); } } }; /** * Callbacks from the {@link MovieView} showing the video playback. */ private MovieView.MovieListener mMovieListener = new MovieView.MovieListener() { @Override public void onMovieStarted() { // We are playing the video now. In PiP mode, we want to show an action item to // pause // the video. updatePictureInPictureActions(R.drawable.ic_pause_24dp, mPause, CONTROL_TYPE_PAUSE, REQUEST_PAUSE); } @Override public void onMovieStopped() { // The video stopped or reached its end. In PiP mode, we want to show an action // item to play the video. updatePictureInPictureActions(R.drawable.ic_play_arrow_24dp, mPlay, CONTROL_TYPE_PLAY, REQUEST_PLAY); } @Override public void onMovieMinimized() { // The MovieView wants us to minimize it. We enter Picture-in-Picture mode now. minimize(); } }; /** * Update the state of pause/resume action item in Picture-inPicture mode. * * @param iconId the icon to be used. * @param title the title text. * @param controlType the type of te action. either {@link #CONTROL_TYPE_PLAY} or {@link #CONTROL_TYPE_PAUSE}. * @param requestCode The request code for the {@link PendingIntent}. */ void updatePictureInPictureActions(@DrawableRes int iconId, String title, int controlType, int requestCode) { final ArrayList<RemoteAction> actions = new ArrayList<>(); // This is the PendingIntent that is invoked when a user clicks on the item. // You need to use distinct request codes for play and pause, or the PendingIntent wont't // be properly updated. final PendingIntent intent = PendingIntent.getBroadcast( VideoPipActivity.this, requestCode, new Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, controlType), 0); final Icon icon = Icon.createWithResource(VideoPipActivity.this, iconId); actions.add(new RemoteAction(icon, title, title, intent)); // Another action item. This is a fixed action. actions.add(new RemoteAction( Icon.createWithResource(VideoPipActivity.this, R.drawable.ic_info_24dp), getString(R.string.info), getString(R.string.info_description), PendingIntent.getActivity( VideoPipActivity.this, REQUEST_INFO, new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.info_uri))), 0) )); mPictureInPictureParamsBuilder.setActions(actions); // This is how you can update action items (or aspect ratio) for Picture-in-Picture mode. // Note this call can happen even when the app is not in PiP mode. In that case, the // arguments will be used for at the next call of #enterPictureInPictureMode. setPictureInPictureParams(mPictureInPictureParamsBuilder.build()); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_pip); // Prepare string resource for Picture-in-Picture actions. mPlay = getString(R.string.play); mPause = getString(R.string.pause); // View references mMovieView = findViewById(R.id.movie); mScrollView = findViewById(R.id.scroll); // Set up the video; it automatically starts. mMovieView.setMovieListener(mMovieListener); findViewById(R.id.pip).setOnClickListener(mOnClickListener); } @Override protected void onStop() { // On entering Picture-in-Picture mode, onPause is called, but not onStop. // For this reason, this is the place where we should pause the video playback. mMovieView.pause(); super.onStop(); } @Override protected void onRestart() { super.onRestart(); if (!isInPictureInPictureMode()) { // Show the video controls so the video can be easily resumed. mMovieView.showControls(); } } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); adjustFullScreen(newConfig); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { adjustFullScreen(getResources().getConfiguration()); } } @Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); if (isInPictureInPictureMode) { // Starts receiving events from action items in PiP mode. mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (null == intent || !ACTION_MEDIA_CONTROL.equals(intent.getAction())) { return; } // This is where we are called back from Picture-in-Picture action items. final int controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0); switch (controlType) { case CONTROL_TYPE_PLAY: mMovieView.play(); break; case CONTROL_TYPE_PAUSE: mMovieView.pause(); break; default: break; } } }; registerReceiver(mReceiver, new IntentFilter(ACTION_MEDIA_CONTROL)); } else { // We are out of PiP mode. We can stop receiving events from it. unregisterReceiver(mReceiver); mReceiver = null; // Show the video controls if the video is not playing if (null != mMovieView && !mMovieView.isPlaying()) { mMovieView.showControls(); } } } /** * Enters Picture-in-Picture mode. */ void minimize() { if (null == mMovieView) { return; } // Hide the controls in Picture-in-Picture mode. mMovieView.hideControls(); // Calculate the aspect ratio of the PiP screen. Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight()); mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build(); enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()); } /** * Adjusts immersive full-screen flags depending on the screen orientation. * * @param config The current {@link Configuration}. */ private void adjustFullScreen(Configuration config) { final View decorView = getWindow().getDecorView(); if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); mScrollView.setVisibility(View.GONE); mMovieView.setAdjustViewBounds(false); } else { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); mScrollView.setVisibility(View.VISIBLE); mMovieView.setAdjustViewBounds(true); } } }
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Android實現簡單畫中畫功能
- Android進程間使用Intent進行通信
- Android四大組件之Activity詳細介紹
- Android中Intent組件的入門學習心得
- Android畫中畫窗口開啟方法