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