简介: 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);
- }
- }
-
- }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。