Android實現(xiàn)視頻的畫中畫功能
簡介: Android 8.0(API 級別 26)允許以畫中畫 (PIP) 模式啟動 Activity。畫中畫是一種特殊類型的多窗口模式,最常用于視頻播放。使用該模式,用戶可以通過固定到屏幕一角的小窗口觀看視頻,同時在應(yīng)用之間進行導(dǎo)航或瀏覽主屏幕上的內(nèi)容。
畫中畫窗口會顯示在屏幕的最上層,位于系統(tǒng)選擇的一角。您可以將畫中畫窗口拖動到其他位置(會自動貼邊)。當(dāng)您點按該窗口時,會看到兩個特殊的控件:全屏切換開關(guān)(位于窗口的中心)和關(guān)閉按鈕(右上角的“X”)。
效果圖:

1、聲明對畫中畫的支持:
默認(rèn)情況下,系統(tǒng)不會自動為應(yīng)用提供畫中畫支持。如果您想在應(yīng)用中支持畫中畫,可以通過將 android:supportsPictureInPicture 設(shè)置為 true,在清單中注冊視頻 Activity。此外,指定您的 Activity 處理布局配置更改,這樣一來,在畫中畫模式轉(zhuǎn)換期間發(fā)生布局更改時,您的 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 必須調(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 進入或退出畫中畫模式時,系統(tǒng)會調(diào)用 Activity.onPictureInPictureModeChanged() 或 Fragment.onPictureInPictureModeChanged() 。在畫中畫模式下,Activity 會在一個小窗口中顯示。在畫中畫模式下,用戶無法與界面元素互動,并且可能很難看清小界面元素的詳細信息。在 Activity 進入畫中畫模式之前移除其他界面元素,并在 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();
}
}
}
完整代碼:
頁面布局文件:
<?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);
}
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android編程之?dāng)?shù)據(jù)庫Sql編程實例分析
這篇文章主要介紹了Android編程之?dāng)?shù)據(jù)庫Sql編程,實例分析了Android操作Sqlite數(shù)據(jù)庫的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04
Android 配置gradle實現(xiàn)VersionCode自增實例
今天小編就為大家分享一篇Android 配置gradle實現(xiàn)VersionCode自增實例,具有很好的 參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android中實現(xiàn)記事本動態(tài)添加行效果
記事本對我們每個人來說再熟悉不過,下面這篇文章主要給大家介紹了在Android中實現(xiàn)記事本動態(tài)添加行效果的相關(guān)資料,這是最近在開發(fā)中遇到的一個小需求,想著分享出來供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。2017-06-06

