经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android实现视频的画中画功能
来源:jb51  时间:2021/8/26 17:21:14  对本文有异议

简介: Android 8.0(API 级别 26)允许以画中画 (PIP) 模式启动 Activity。画中画是一种特殊类型的多窗口模式,最常用于视频播放。使用该模式,用户可以通过固定到屏幕一角的小窗口观看视频,同时在应用之间进行导航或浏览主屏幕上的内容。

画中画窗口会显示在屏幕的最上层,位于系统选择的一角。您可以将画中画窗口拖动到其他位置(会自动贴边)。当您点按该窗口时,会看到两个特殊的控件:全屏切换开关(位于窗口的中心)和关闭按钮(右上角的“X”)。

效果图:

1、声明对画中画的支持:

默认情况下,系统不会自动为应用提供画中画支持。如果您想在应用中支持画中画,可以通过将 android:supportsPictureInPicture 设置为 true,在清单中注册视频 Activity。此外,指定您的 Activity 处理布局配置更改,这样一来,在画中画模式转换期间发生布局更改时,您的 Activity 就不会重新启动。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. package="com.csu.pictureinpicture">
  5.  
  6. <application
  7. android:allowBackup="true"
  8. android:icon="@mipmap/ic_launcher"
  9. android:label="@string/app_name"
  10. android:roundIcon="@mipmap/ic_launcher_round"
  11. android:supportsRtl="true"
  12. android:theme="@style/AppTheme"
  13. tools:ignore="GoogleAppIndexingWarning">
  14.  
  15. <activity
  16. android:name=".VideoPipActivity"
  17. android:resizeableActivity="true"
  18. android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
  19. android:supportsPictureInPicture="true" />
  20. <activity android:name=".MediaSessionPlaybackActivity"
  21. android:resizeableActivity="true"
  22. android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
  23. android:supportsPictureInPicture="true">
  24.  
  25. <intent-filter>
  26. <action android:name="android.intent.action.MAIN" />
  27.  
  28. <category android:name="android.intent.category.LAUNCHER" />
  29. </intent-filter>
  30. </activity>
  31.  
  32. </application>
  33.  
  34. </manifest>

2、将 Activity 切换到画中画模式:

如要进入画中画模式,Activity 必须调用enterPictureInPictureMode()

  1. /**
  2. * Enters Picture-in-Picture mode.
  3. */
  4. void minimize() {
  5. if (null == mMovieView) {
  6. return;
  7. }
  8.  
  9. // Hide the controls in Picture-in-Picture mode.
  10. mMovieView.hideControls();
  11. // Calculate the aspect ratio of the PiP screen.
  12. Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight());
  13. mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
  14. enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());
  15. }

3、处理画中画模式下的界面元素

当 Activity 进入或退出画中画模式时,系统会调用 Activity.onPictureInPictureModeChanged() Fragment.onPictureInPictureModeChanged() 。在画中画模式下,Activity 会在一个小窗口中显示。在画中画模式下,用户无法与界面元素互动,并且可能很难看清小界面元素的详细信息。在 Activity 进入画中画模式之前移除其他界面元素,并在 Activity 再次变为全屏时恢复这些元素:

  1. @Override
  2. public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
  3. super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
  4. if (isInPictureInPictureMode) {
  5. // Starts receiving events from action items in PiP mode.
  6. mReceiver = new BroadcastReceiver() {
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. if (null == intent || !ACTION_MEDIA_CONTROL.equals(intent.getAction())) {
  10. return;
  11. }
  12.  
  13. // This is where we are called back from Picture-in-Picture action items.
  14. final int controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0);
  15. switch (controlType) {
  16. case CONTROL_TYPE_PLAY:
  17. mMovieView.play();
  18. break;
  19. case CONTROL_TYPE_PAUSE:
  20. mMovieView.pause();
  21. break;
  22. default:
  23. break;
  24. }
  25. }
  26. };
  27. registerReceiver(mReceiver, new IntentFilter(ACTION_MEDIA_CONTROL));
  28. } else {
  29. // We are out of PiP mode. We can stop receiving events from it.
  30. unregisterReceiver(mReceiver);
  31. mReceiver = null;
  32. // Show the video controls if the video is not playing
  33. if (null != mMovieView && !mMovieView.isPlaying()) {
  34. mMovieView.showControls();
  35. }
  36. }
  37. }

完整代码:

页面布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:id="@+id/activity_video"
  9. android:orientation="vertical"
  10. tools:context=".VideoPipActivity">
  11.  
  12. <com.csu.pictureinpicture.widget.MovieView
  13. android:id="@+id/movie"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:adjustViewBounds="true"
  17. android:src="@raw/vid_bigbuckbunny"
  18. android:title="@string/title_bigbuckbunny"/>
  19.  
  20. <ScrollView
  21. android:id="@+id/scroll"
  22. android:layout_width="match_parent"
  23. android:layout_height="0dp"
  24. android:layout_weight="1">
  25.  
  26. <LinearLayout
  27. android:id="@+id/vertical"
  28. android:layout_width="match_parent"
  29. android:layout_height="wrap_content"
  30. android:orientation="vertical"
  31. android:paddingBottom="@dimen/activity_vertical_margin"
  32. android:paddingEnd="@dimen/activity_horizontal_margin"
  33. android:paddingStart="@dimen/activity_horizontal_margin"
  34. android:paddingTop="@dimen/activity_vertical_margin">
  35.  
  36. <Button
  37. android:id="@+id/pip"
  38. android:layout_width="wrap_content"
  39. android:layout_height="wrap_content"
  40. android:text="@string/enter_picture_in_picture"/>
  41.  
  42. </LinearLayout>
  43.  
  44. </ScrollView>
  45.  
  46. </LinearLayout>

Activity文件:

  1. public class VideoPipActivity extends AppCompatActivity {
  2.  
  3. /**
  4. * Intent action for media controls from Picture-in-Picture mode.
  5. */
  6. private static final String ACTION_MEDIA_CONTROL = "media_control";
  7.  
  8. /**
  9. * Intent extra for media controls from Picture-in-Picture mode.
  10. */
  11. private static final String EXTRA_CONTROL_TYPE = "control_type";
  12.  
  13. /**
  14. * The request code for play action PendingIntent.
  15. */
  16. private static final int REQUEST_PLAY = 1;
  17.  
  18. /**
  19. * The request code for pause action PendingIntent.
  20. */
  21. private static final int REQUEST_PAUSE = 2;
  22.  
  23. /**
  24. * The request code for info action PendingIntent.
  25. */
  26. private static final int REQUEST_INFO = 3;
  27.  
  28. /**
  29. * The intent extra value for play action.
  30. */
  31. private static final int CONTROL_TYPE_PLAY = 1;
  32.  
  33. /**
  34. * The intent extra value for pause action.
  35. */
  36. private static final int CONTROL_TYPE_PAUSE = 2;
  37.  
  38. /**
  39. * The arguments to be used for Picture-in-Picture mode.
  40. */
  41. private final PictureInPictureParams.Builder mPictureInPictureParamsBuilder =
  42. new PictureInPictureParams.Builder();
  43.  
  44. /**
  45. * This shows the video.
  46. */
  47. private MovieView mMovieView;
  48.  
  49. /**
  50. * The bottom half of the screen; hidden on landscape.
  51. */
  52. private ScrollView mScrollView;
  53.  
  54. /**
  55. * A {@link BroadcastReceiver} to receive action item events from Picture-in-Picture mode.
  56. */
  57. private BroadcastReceiver mReceiver;
  58.  
  59. private String mPlay;
  60. private String mPause;
  61.  
  62. private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
  63. @Override
  64. public void onClick(View v) {
  65. if (v.getId() == R.id.pip) {
  66. minimize();
  67. }
  68. }
  69. };
  70.  
  71. /**
  72. * Callbacks from the {@link MovieView} showing the video playback.
  73. */
  74. private MovieView.MovieListener mMovieListener = new MovieView.MovieListener() {
  75. @Override
  76. public void onMovieStarted() {
  77. // We are playing the video now. In PiP mode, we want to show an action item to
  78. // pause
  79. // the video.
  80. updatePictureInPictureActions(R.drawable.ic_pause_24dp, mPause, CONTROL_TYPE_PAUSE, REQUEST_PAUSE);
  81. }
  82.  
  83. @Override
  84. public void onMovieStopped() {
  85. // The video stopped or reached its end. In PiP mode, we want to show an action
  86. // item to play the video.
  87. updatePictureInPictureActions(R.drawable.ic_play_arrow_24dp, mPlay, CONTROL_TYPE_PLAY, REQUEST_PLAY);
  88. }
  89.  
  90. @Override
  91. public void onMovieMinimized() {
  92. // The MovieView wants us to minimize it. We enter Picture-in-Picture mode now.
  93. minimize();
  94. }
  95. };
  96.  
  97. /**
  98. * Update the state of pause/resume action item in Picture-inPicture mode.
  99. *
  100. * @param iconId the icon to be used.
  101. * @param title the title text.
  102. * @param controlType the type of te action. either {@link #CONTROL_TYPE_PLAY} or {@link #CONTROL_TYPE_PAUSE}.
  103. * @param requestCode The request code for the {@link PendingIntent}.
  104. */
  105. void updatePictureInPictureActions(@DrawableRes int iconId, String title, int controlType, int requestCode) {
  106. final ArrayList<RemoteAction> actions = new ArrayList<>();
  107.  
  108. // This is the PendingIntent that is invoked when a user clicks on the item.
  109. // You need to use distinct request codes for play and pause, or the PendingIntent wont't
  110. // be properly updated.
  111. final PendingIntent intent = PendingIntent.getBroadcast(
  112. VideoPipActivity.this,
  113. requestCode,
  114. new Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, controlType),
  115. 0);
  116. final Icon icon = Icon.createWithResource(VideoPipActivity.this, iconId);
  117. actions.add(new RemoteAction(icon, title, title, intent));
  118.  
  119. // Another action item. This is a fixed action.
  120. actions.add(new RemoteAction(
  121. Icon.createWithResource(VideoPipActivity.this, R.drawable.ic_info_24dp),
  122. getString(R.string.info),
  123. getString(R.string.info_description),
  124. PendingIntent.getActivity(
  125. VideoPipActivity.this,
  126. REQUEST_INFO,
  127. new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.info_uri))), 0)
  128. ));
  129.  
  130. mPictureInPictureParamsBuilder.setActions(actions);
  131.  
  132. // This is how you can update action items (or aspect ratio) for Picture-in-Picture mode.
  133. // Note this call can happen even when the app is not in PiP mode. In that case, the
  134. // arguments will be used for at the next call of #enterPictureInPictureMode.
  135. setPictureInPictureParams(mPictureInPictureParamsBuilder.build());
  136. }
  137.  
  138. @Override
  139. protected void onCreate(Bundle savedInstanceState) {
  140. super.onCreate(savedInstanceState);
  141. setContentView(R.layout.activity_video_pip);
  142.  
  143. // Prepare string resource for Picture-in-Picture actions.
  144. mPlay = getString(R.string.play);
  145. mPause = getString(R.string.pause);
  146.  
  147. // View references
  148. mMovieView = findViewById(R.id.movie);
  149. mScrollView = findViewById(R.id.scroll);
  150.  
  151. // Set up the video; it automatically starts.
  152. mMovieView.setMovieListener(mMovieListener);
  153. findViewById(R.id.pip).setOnClickListener(mOnClickListener);
  154. }
  155.  
  156. @Override
  157. protected void onStop() {
  158. // On entering Picture-in-Picture mode, onPause is called, but not onStop.
  159. // For this reason, this is the place where we should pause the video playback.
  160. mMovieView.pause();
  161. super.onStop();
  162. }
  163.  
  164. @Override
  165. protected void onRestart() {
  166. super.onRestart();
  167. if (!isInPictureInPictureMode()) {
  168. // Show the video controls so the video can be easily resumed.
  169. mMovieView.showControls();
  170. }
  171. }
  172.  
  173. @Override
  174. public void onConfigurationChanged(@NonNull Configuration newConfig) {
  175. super.onConfigurationChanged(newConfig);
  176. adjustFullScreen(newConfig);
  177. }
  178.  
  179. @Override
  180. public void onWindowFocusChanged(boolean hasFocus) {
  181. super.onWindowFocusChanged(hasFocus);
  182. if (hasFocus) {
  183. adjustFullScreen(getResources().getConfiguration());
  184. }
  185. }
  186.  
  187. @Override
  188. public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
  189. super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
  190. if (isInPictureInPictureMode) {
  191. // Starts receiving events from action items in PiP mode.
  192. mReceiver = new BroadcastReceiver() {
  193. @Override
  194. public void onReceive(Context context, Intent intent) {
  195. if (null == intent || !ACTION_MEDIA_CONTROL.equals(intent.getAction())) {
  196. return;
  197. }
  198.  
  199. // This is where we are called back from Picture-in-Picture action items.
  200. final int controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0);
  201. switch (controlType) {
  202. case CONTROL_TYPE_PLAY:
  203. mMovieView.play();
  204. break;
  205. case CONTROL_TYPE_PAUSE:
  206. mMovieView.pause();
  207. break;
  208. default:
  209. break;
  210. }
  211. }
  212. };
  213. registerReceiver(mReceiver, new IntentFilter(ACTION_MEDIA_CONTROL));
  214. } else {
  215. // We are out of PiP mode. We can stop receiving events from it.
  216. unregisterReceiver(mReceiver);
  217. mReceiver = null;
  218. // Show the video controls if the video is not playing
  219. if (null != mMovieView && !mMovieView.isPlaying()) {
  220. mMovieView.showControls();
  221. }
  222. }
  223. }
  224.  
  225. /**
  226. * Enters Picture-in-Picture mode.
  227. */
  228. void minimize() {
  229. if (null == mMovieView) {
  230. return;
  231. }
  232.  
  233. // Hide the controls in Picture-in-Picture mode.
  234. mMovieView.hideControls();
  235. // Calculate the aspect ratio of the PiP screen.
  236. Rational aspectRatio = new Rational(mMovieView.getWidth(), mMovieView.getHeight());
  237. mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
  238. enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());
  239. }
  240.  
  241. /**
  242. * Adjusts immersive full-screen flags depending on the screen orientation.
  243. *
  244. * @param config The current {@link Configuration}.
  245. */
  246. private void adjustFullScreen(Configuration config) {
  247. final View decorView = getWindow().getDecorView();
  248. if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
  249. decorView.setSystemUiVisibility(
  250. View.SYSTEM_UI_FLAG_LAYOUT_STABLE
  251. | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  252. | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  253. | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  254. | View.SYSTEM_UI_FLAG_FULLSCREEN
  255. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  256. mScrollView.setVisibility(View.GONE);
  257. mMovieView.setAdjustViewBounds(false);
  258. } else {
  259. decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
  260. mScrollView.setVisibility(View.VISIBLE);
  261. mMovieView.setAdjustViewBounds(true);
  262. }
  263. }
  264.  
  265. }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号