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);

}

}

}

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

以上是 Android实现视频的画中画功能 的全部内容, 来源链接: utcz.com/p/243855.html

回到顶部