Android Oreo画中画

时间:2020-02-23 14:29:07  来源:igfitidea点击:

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