Android Oreo画中画
您曾经想在浏览网络时在Android手机上观看视频吗?每个人都渴望得到这一点。
感谢Android Oreo,我们现在有了画中画功能,可以开发出色的Android应用程序。
在本教程中,我们将开发一个应用程序,在浏览视频列表或者智能手机上的其他应用程序的同时,我们可以预览视频。
您会在YouTube或者FaceBook应用程序中同时观看视频和执行其他操作时看到此行为。
Android画中画
伴随Android Nougat更新,Android TV有了画中画支持。
因此,它有望在Oreo中用于手机。
默认情况下,画中画(PiP)功能未启用。
因此,您需要在AndroidManifest.xml文件中为需要PiP的活动设置以下属性。
<activity
android:name=".PiPActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:label="@string/title_activity_pi_p"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:theme="@style/AppTheme.NoActionBar"
必须设置android:configChanges以确保在更改屏幕尺寸时,活动不会丢失任何数据。
android:resizeableActivity确保在触发画中画时可以调整活动的大小。
在我们的活动中,有三种主要的画中画支持方法:
enterPictureInPictureMode():我们在此处传递PiP构建器的实例。
此方法是启动PiP的方法。onPictureInPictureModeChanged():发生画中画时触发。
其中我们可以隐藏在画中画启用模式下没有的UI元素。onUserLeaveHint():当用户按下主页/最近的选项进入应用程序外部时,将触发此事件。
我们可以其中设置画中画。
我们需要确保不要在onPause()/onStop()方法中暂停/停止视频。
Android画中画代码
下面给出了activity_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
</RelativeLayout>
RecyclerView的每一行都是从cardview_item_row.xml文件填充的:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/cardView"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/txtName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
</LinearLayout>
</android.support.v7.widget.CardView>
MainActivity.java类的代码如下:
package com.theitroad.androidpictureinpicture;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.ItemListener {
private ArrayList<Model> videoList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new LinearLayoutManager(this));
populateArrayList();
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(videoList, this);
recyclerView.setAdapter(recyclerViewAdapter);
}
private void populateArrayList() {
videoList.add(new Model("Big Buck Bunny", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"));
videoList.add(new Model("We are going on bull run", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"));
videoList.add(new Model("Volkswagen GTI Review", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4"));
videoList.add(new Model("For Bigger Blazes", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"));
videoList.add(new Model("Subaru Outback On Street And Dirt", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4"));
videoList.add(new Model("What care can you get for ten grand?", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4"));
}
@Override
public void onItemClick(Model model) {
startActivity(new Intent(this, PiPActivity.class).putExtra("videoUrl", model.videoUrl));
}
}
我们创建了一个Model.java类,其中包含RecyclerViewAdapter类的数据。
package com.theitroad.androidpictureinpicture;
public class Model {
public String name, videoUrl;
public Model(String name, String videoUrl) {
this.name = name;
this.videoUrl = videoUrl;
}
}
下面给出了RecyclerViewAdapter.java类的代码:
package com.theitroad.androidpictureinpicture;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.CryptoViewHolder> {
private ArrayList<Model> videoList;
private ItemListener mItemListener;
public class CryptoViewHolder extends RecyclerView.ViewHolder {
private TextView mName;
private CardView cardView;
public CryptoViewHolder(View itemView) {
super(itemView);
mName = itemView.findViewById(R.id.txtName);
cardView = itemView.findViewById(R.id.cardView);
}
}
public RecyclerViewAdapter(ArrayList<Model> videoList, ItemListener itemListener) {
this.videoList = videoList;
this.mItemListener = itemListener;
}
@Override
public CryptoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_item_row, parent, false);
return new CryptoViewHolder(itemView);
}
@Override
public void onBindViewHolder(CryptoViewHolder holder, final int position) {
holder.mName.setText(videoList.get(position).name);
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItemListener.onItemClick(videoList.get(position));
}
});
}
@Override
public int getItemCount() {
return videoList.size();
}
public interface ItemListener {
void onItemClick(Model model);
}
}
" onItemClick"触发了对" PiPActivity"的调用,并传递了videoUrl。
下面给出了activity_pip.xml类的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PiPActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_pi_p"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_menu_set_as"
</android.support.design.widget.CoordinatorLayout>
下面给出了content_pi_p.xml的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".PiPActivity"
tools:showIn="@layout/activity_pip">
<VideoView
android:id="@+id/videoView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
</android.support.constraint.ConstraintLayout>
PiPActivity.java类的代码如下:
package com.theitroad.androidpictureinpicture;
import android.app.PictureInPictureParams;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Rational;
import android.view.View;
import android.widget.MediaController;
import android.widget.VideoView;
@RequiresApi(api = Build.VERSION_CODES.O)
public class PiPActivity extends AppCompatActivity {
VideoView videoView;
FloatingActionButton fab;
PictureInPictureParams.Builder pictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pip);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
videoView = findViewById(R.id.videoView);
fab = findViewById(R.id.fab);
final MediaController mediacontroller = new MediaController(this);
mediacontroller.setAnchorView(videoView);
String videoUrl = getIntent().getStringExtra("videoUrl");
videoView.setMediaController(mediacontroller);
videoView.setVideoURI(Uri.parse(videoUrl));
videoView.requestFocus();
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startPictureInPictureFeature();
}
});
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
videoView.setMediaController(mediacontroller);
mediacontroller.setAnchorView(videoView);
}
});
}
});
}
private void startPictureInPictureFeature() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Rational aspectRatio = new Rational(videoView.getWidth(), videoView.getHeight());
pictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
enterPictureInPictureMode(pictureInPictureParamsBuilder.build());
}
}
@Override
public void onUserLeaveHint() {
if (!isInPictureInPictureMode()) {
Rational aspectRatio = new Rational(videoView.getWidth(), videoView.getHeight());
pictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
enterPictureInPictureMode(pictureInPictureParamsBuilder.build());
}
}
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
Configuration newConfig) {
if (isInPictureInPictureMode) {
fab.setVisibility(View.GONE);
toolbar.setVisibility(View.GONE);
} else {
fab.setVisibility(View.VISIBLE);
toolbar.setVisibility(View.VISIBLE);
}
}
@Override
public void onNewIntent(Intent i) {
updateVideoView(i);
}
private void updateVideoView(Intent i) {
String videoUrl = i.getStringExtra("videoUrl");
videoView.setVideoURI(Uri.parse(videoUrl));
videoView.requestFocus();
}
}
在" startPictureInPictureFeature()"中,在以图片模式启动图片之前,我们将VideoView的宽度和高度更改为预览大小。
在onNewIntent()方法内部,我们更新了VideoView。
在" onPictureInPictureModeChanged"内部,我们隐藏了工具列以及FloatingActionButton。
当完成对现有活动的意图时,将触发onNewIntent。
在此示例中,保留一个PiP活动实例是有意义的。
我们的AndroidManifest.xml文件定义如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.theitroad.androidpictureinpicture">
<uses-permission android:name="android.permission.INTERNET"
<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"
<category android:name="android.intent.category.LAUNCHER"
</intent-filter>
</activity>
<activity
android:name=".PiPActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:label="@string/title_activity_pi_p"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:theme="@style/AppTheme.NoActionBar"
</application>
</manifest>
PictureInPicture功能提供了一个内置图标,可以调整预览大小并将其拖到屏幕上的任何位置。

