android oreo

Ever wanted to watch videos on your Android phone while you browse the web? Everyone always desires this. Thanks to Android Oreo, we now have Picture In Picture feature to develop awesome Android Applications.

您是否曾经想在浏览网络时在Android手机上观看视频? 每个人都一直渴望这一点。 感谢Android Oreo,我们现在有了画中画功能,可以开发出色的Android应用程序。

In this tutorial, we’ll be developing an application where we can view a Video in a preview while we browse through the list of our videos or other applications on our smartphone. You would see this behavior in YouTube or FaceBook app while watching videos and doing other things at the same time in the app.

在本教程中,我们将开发一个应用程序,在浏览视频列表或智能手机上的其他应用程序时,可以预览视频。 您会在YouTube或FaceBook应用程序中同时观看视频和执行其他操作时看到此行为。

(Android Picture In Picture)

Picture In Picture support came up for Android TV with the Android Nougat update. So it was expected to be there for phones in Oreo. By default, Picture In Picture (PiP) feature isn’t enabled for activities.

随着Android Nougat更新,对Android TV进行了画中画支持。 因此,预计在奥利奥(Oreo)中会有电话。 默认情况下,活动未启用画中画(PiP)功能。

So you need to set the following attributes in the AndroidManifest.xml file for the activity which requires 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 must be set to make sure that the Activity does not lose any data while changing the screen size particularly.

必须设置android:configChanges ,以确保在更改屏幕尺寸时,活动不会丢失任何数据。

android:resizeableActivity makes sure that the activity can be resized when the PiP is triggered.

android:resizeableActivity确保在触发画中画时可以调整活动的大小。

In our Activity there are three major methods for PiP support:

在我们的活动中,有三种主要的画中画支持方法:

enterPictureInPictureMode(): We pass an instance of the PiP builder in here. This method is what starts the PiP. enterPictureInPictureMode() :我们在此处传递PiP构建器的实例。 此方法就是启动PiP的方法。

onPictureInPictureModeChanged(): gets triggered when the PiP has occurred. Here we can hide the UI elements which we don’t what in the PiP enabled mode. onPictureInPictureModeChanged() :发生画中画时触发。 在这里,我们可以隐藏在PiP启用模式下没有的UI元素。

onUserLeaveHint(): This gets triggered when the user presses home/recents options to come outside the application. We can set the PiP inside it. We need to make sure that we don’t pause/stop the video in the onPause()/onStop() methods. onUserLeaveHint() :当用户按下主页/最近的选项进入应用程序外部时,将触发此事件。 我们可以在其中设置画中画。 我们需要确保不要在onPause()/ onStop()方法中暂停/停止视频。

Now let the code do the rest of the talking. In the following section, we’ll create a RecyclerView which contains a list of Videos. The Videos would be played in the VideoView.

现在让代码完成其余的讨论。 在以下部分中,我们将创建一个RecyclerView,其中包含视频列表。 视频将在VideoView中播放。

We’ve used a public set of video urls available here

我们使用的公共集视频的网址提供点击这里

(Android Picture in Picture Project Structure)

(Android Picture in Picture Code)

The code for the activity_main.xml layout is given below:

下面给出了activity_main.xml布局的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https:///apk/res/android"
    xmlns:app="https:///apk/res-auto"
    xmlns:tools="https:///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>

Each row of the RecyclerView is populated from cardview_item_row.xml file:

RecyclerView的每一行都是从cardview_item_row.xml文件填充的:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="https:///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>

The code for the MainActivity.java class is given below:

MainActivity.java类的代码如下:

package com.journaldev.androidpictureinpicture;

import android.content.Intent;
import .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(.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:///gtv-videos-bucket/sample/BigBuckBunny.mp4"));
        videoList.add(new Model("We are going on bull run", "https:///gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"));
        videoList.add(new Model("Volkswagen GTI Review", "https:///gtv-videos-bucket/sample/VolkswagenGTIReview.mp4"));
        videoList.add(new Model("For Bigger Blazes", "https:///gtv-videos-bucket/sample/ForBiggerBlazes.mp4"));
        videoList.add(new Model("Subaru Outback On Street And Dirt", "https:///gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4"));
        videoList.add(new Model("What care can you get for ten grand?", "https:///gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4"));
    }

    @Override
    public void onItemClick(Model model) {

        startActivity(new Intent(this, PiPActivity.class).putExtra("videoUrl", model.videoUrl));

    }
}

We’ve created a Model.java class which holds the data for the RecyclerViewAdapter class.

我们创建了一个Model.java类,其中包含RecyclerViewAdapter类的数据。

package com.journaldev.androidpictureinpicture;

public class Model {

    public String name, videoUrl;

    public Model(String name, String videoUrl) {
         = name;
        this.videoUrl = videoUrl;
    }

}

The code for the RecyclerViewAdapter.java class is given below:

下面给出了RecyclerViewAdapter.java类的代码:

package com.journaldev.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(.txtName);
            cardView = itemView.findViewById(.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 triggers a call to the PiPActivity with the videoUrl being passed.

onItemClick通过videoUrl触发对PiPActivity的调用。

The code for the activity_pip.xml class is given below:

下面给出了activity_pip.xml类的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https:///apk/res/android"
    xmlns:app="https:///apk/res-auto"
    xmlns:tools="https:///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>

The code for the content_pi_p.xml is given below:

下面给出了content_pi_p.xml的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https:///apk/res/android"
    xmlns:app="https:///apk/res-auto"
    xmlns:tools="https:///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>

The code for the PiPActivity.java class is given below:

PiPActivity.java类的代码如下:

package com.journaldev.androidpictureinpicture;

import .PictureInPictureParams;
import android.content.Intent;
import android.content.res.Configuration;
import .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 .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(.toolbar);
        setSupportActionBar(toolbar);


        videoView = findViewById(.videoView);
        fab = findViewById(.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();
    }

}

In the startPictureInPictureFeature() we change the width and height of the VideoView to a preview size before starting the picture in picture mode.

在startPictureInPictureFeature()我们在以图片模式启动图片之前将VideoView的宽度和高度更改为预览大小。

Inside the onNewIntent() method, we update the VideoView.

在onNewIntent()方法中,我们更新了VideoView。

Inside the onPictureInPictureModeChanged we hide the toolbar as well as the FloatingActionButton.

在onPictureInPictureModeChanged内部,我们隐藏了工具栏以及FloatingActionButton。

onNewIntent gets triggered when an intent to an existing activity is done. In this example, it makes sense to keep a single instance of the PiP Activity.

当完成对现有活动的意图时,将触发onNewIntent。 在此示例中,保留一个PiP活动实例是有意义的。

Our AndroidManifest.xml file is defined below:

我们的AndroidManifest.xml文件定义如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https:///apk/res/android"
    package="com.journaldev.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 feature provides an inbuilt icon to resize the preview and as well as drag it on the screen anywhere.

PictureInPicture功能提供了一个内置图标,可以调整预览的大小并将其拖到屏幕上的任何位置。

The output of the above application in action is given below:

上面应用程序的输出如下:

Just double tap the VideoView to enable MediaControls.

只需双击VideoView以启用MediaControls。

Look how easy it is to implement PictureInPicture in your Android Application!

看看在您的Android应用程序中实现PictureInPicture多么容易!

This brings an end to this tutorial. You can download the project from the link below:

本教程到此结束。 您可以从下面的链接下载项目:

AndroidPictureInPicture AndroidPictureInPicture

GitHub Repository. GitHub Repository下载源代码。

翻译自: https://www.journaldev.com/20907/android-oreo-picture-in-picture

android oreo