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功能提供了一个内置图标,可以调整预览大小并将其拖到屏幕上的任何位置。