Android實(shí)現(xiàn)視頻的畫中畫功能
簡(jiǎn)介: Android 8.0(API 級(jí)別 26)允許以畫中畫 (PIP) 模式啟動(dòng) Activity。畫中畫是一種特殊類型的多窗口模式,最常用于視頻播放。使用該模式,用戶可以通過(guò)固定到屏幕一角的小窗口觀看視頻,同時(shí)在應(yīng)用之間進(jìn)行導(dǎo)航或?yàn)g覽主屏幕上的內(nèi)容。
畫中畫窗口會(huì)顯示在屏幕的最上層,位于系統(tǒng)選擇的一角。您可以將畫中畫窗口拖動(dòng)到其他位置(會(huì)自動(dòng)貼邊)。當(dāng)您點(diǎn)按該窗口時(shí),會(huì)看到兩個(gè)特殊的控件:全屏切換開關(guān)(位于窗口的中心)和關(guān)閉按鈕(右上角的“X”)。
效果圖:
1、聲明對(duì)畫中畫的支持:
默認(rèn)情況下,系統(tǒng)不會(huì)自動(dòng)為應(yīng)用提供畫中畫支持。如果您想在應(yīng)用中支持畫中畫,可以通過(guò)將 android:supportsPictureInPicture 設(shè)置為 true,在清單中注冊(cè)視頻 Activity。此外,指定您的 Activity 處理布局配置更改,這樣一來(lái),在畫中畫模式轉(zhuǎn)換期間發(fā)生布局更改時(shí),您的 Activity 就不會(huì)重新啟動(dòng)。
<?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 切換到畫中畫模式:
如要進(jìn)入畫中畫模式,Activity 必須調(diào)用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、處理畫中畫模式下的界面元素
當(dāng) Activity 進(jìn)入或退出畫中畫模式時(shí),系統(tǒng)會(huì)調(diào)用 Activity.onPictureInPictureModeChanged() 或 Fragment.onPictureInPictureModeChanged() 。在畫中畫模式下,Activity 會(huì)在一個(gè)小窗口中顯示。在畫中畫模式下,用戶無(wú)法與界面元素互動(dòng),并且可能很難看清小界面元素的詳細(xì)信息。在 Activity 進(jìn)入畫中畫模式之前移除其他界面元素,并在 Activity 再次變?yōu)槿習(xí)r恢復(fù)這些元素:
@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(); } } }
完整代碼:
頁(yè)面布局文件:
<?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); } } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android編程之?dāng)?shù)據(jù)庫(kù)Sql編程實(shí)例分析
這篇文章主要介紹了Android編程之?dāng)?shù)據(jù)庫(kù)Sql編程,實(shí)例分析了Android操作Sqlite數(shù)據(jù)庫(kù)的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04Flutter Image實(shí)現(xiàn)圖片加載
這篇文章主要為大家詳細(xì)介紹了Flutter Image實(shí)現(xiàn)圖片加載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07Android 配置gradle實(shí)現(xiàn)VersionCode自增實(shí)例
今天小編就為大家分享一篇Android 配置gradle實(shí)現(xiàn)VersionCode自增實(shí)例,具有很好的 參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Android中實(shí)現(xiàn)記事本動(dòng)態(tài)添加行效果
記事本對(duì)我們每個(gè)人來(lái)說(shuō)再熟悉不過(guò),下面這篇文章主要給大家介紹了在Android中實(shí)現(xiàn)記事本動(dòng)態(tài)添加行效果的相關(guān)資料,這是最近在開發(fā)中遇到的一個(gè)小需求,想著分享出來(lái)供大家參考學(xué)習(xí),需要的朋友們下面來(lái)一起看看吧。2017-06-06Android Spinner與適配器模式詳解及實(shí)例代碼
這篇文章主要介紹了Android Spinner與適配器模式詳解相關(guān)資料,并附代碼實(shí)例,需要的朋友可以參考下2016-10-10