Horizontal RecyclerView inside a Vertical RecyclerView

This is a simple tutorial to develop a scrolling horizontal RecyclerView inside a vertical RecyclerView in Android.

First, we will create the main layout that shows the vertical RecyclerView:

courses_fragment.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:paddingLeft="15dp"
    android:paddingRight="15dp">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id=“@+id/vertical_courses_list"
        android:visibility="gone"/>


</RelativeLayout>

It’s a simple relative layout with a RecyclerView in it.

Now let’s create the layout for each item of the vertical RecyclerView:

courses_item.xml

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="15dp"
    android:layout_marginBottom="15dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true">

        <TextView
            android:id="@+id/course_item_name_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/RegularTextStyle"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/horizontal_list"
            android:layout_width="match_parent"
            android:layout_marginTop="5dp"
            android:layout_height="30dp"
            android:visibility="visible"/>

    </LinearLayout>
    <ImageView
    		android:layout_width="wrap_content"
    		android:layout_height="wrap_content"
    		android:layout_alignParentRight="true"
    		android:layout_centerVertical="true"
    		android:src="@drawable/ic_play_circle_outline_white_24dp"
    		android:tint="@color/colorAccent"/>


</RelativeLayout>

We’ve made a Relative layout with a TextView that displays a Name, an Arrow to the right, and an inside RecyclerView below the Name.

Note: it is important that the inner RecyclerView has layout_height assigned, it won’t work with match_parent or wrap_content.

The same way we created a layout for each item of the main RecyclerView, we will have to create a new layout for each item of the inner horizontal RecyclerView: 

courses_horizontal_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginRight="3dp">

    <TextView
        android:id="@+id/horizontal_item_text"
        android:textColor="@color/white"
        android:layout_width="match_parent"
        android:padding="5dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"/>

</LinearLayout>

Good! We are done with xml files. Now we have to create the adapters for each RecyclerView. Let’s start with inner Horizontal RecyclerView adapter:

HorizontalRVAdapter

public class HorizontalRVAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> mDataList;
    private int mRowIndex = -1;

    public HorizontalRVAdapter() {
    }

    public void setData(List<String> data) {
        if (mDataList != data) {
            mDataList = data;
            notifyDataSetChanged();
        }
    }

    public void setRowIndex(int index) {
        mRowIndex = index;
    }

    private class ItemViewHolder extends RecyclerView.ViewHolder {

        private TextView text;

        public ItemViewHolder(View itemView) {
            super(itemView);
            text = (TextView) itemView.findViewById(R.id.horizontal_item_text);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        View itemView = LayoutInflater.from(context).inflate(R.layout.courses_horizontal_item, parent, false);
        ItemViewHolder holder = new ItemViewHolder(itemView);
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder rawHolder, int position) {
        ItemViewHolder holder = (ItemViewHolder) rawHolder;
        holder.text.setText(mDataList.get(position));
        holder.itemView.setTag(position);
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

}

Here we inflate the layout for each item of the horizontal RecyclerView and set data to each view. In this case we only have a TextView which is filled with a String from List<String>, but views and lists can be customized and be more complex according to each need.

Now let’s wrap it all together in the main RecyclerView adapter:

CourseRVAdapter

public class CourseRVAdapter extends RecyclerView.Adapter<CourseRVAdapter.SimpleViewHolder> {

    private final Context mContext;
    private static List<Nugget> mData;
    private static RecyclerView horizontalList;

    public static class SimpleViewHolder extends RecyclerView.ViewHolder {
        public final TextView title;
        private HorizontalRVAdapter horizontalAdapter;

        public SimpleViewHolder(View view) {
            super(view);
            Context context = itemView.getContext();
            title = (TextView) view.findViewById(R.id.course_item_name_tv);
            horizontalList = (RecyclerView) itemView.findViewById(R.id.horizontal_list);
            horizontalList.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
            horizontalAdapter = new HorizontalRVAdapter();
            horizontalList.setAdapter(horizontalAdapter);
        }
    }

    public CourseRVAdapter(Context context, List<Nugget> data) {
        mContext = context;
        if (data != null)
            mData = new ArrayList<>(data);
        else mData = new ArrayList<>();
    }

    public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view = LayoutInflater.from(mContext).inflate(R.layout.courses_item, parent, false);
        return new SimpleViewHolder(view);
    }

    @Override
    public void onBindViewHolder(SimpleViewHolder holder, final int position) {
        holder.title.setText(mData.get(position).getTitle());
        holder.horizontalAdapter.setData(mData.get(position).getTags()); // List of Strings
        holder.horizontalAdapter.setRowIndex(position);
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }
    
}

And finally, we set the main RecyclerView adapter in our Activity/Fragment:

private CourseRVAdapter adapter;

… 

// Setting RecyclerView
coursesRecyclerView.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
coursesRecyclerView.setLayoutManager(llm);
// nuggetsList is an ArrayList of Custom Objects, in this case  Nugget.class
adapter = new CourseRVAdapter(getActivity(), nuggetsList);
coursesRecyclerView.setAdapter(adapter);

ExoPlayer – Add simple audio player to Android

If you have ever had to develop an Android app that played audio or video, you must have heard of MediaPlayer, a quick solution provided by Android framework for playing media.

Yes, it is simple to use. With only a few lines of code you can reproduce basic audio or video. But what if you need more features?

ExoPlayer

ExoPlayer is an open sourced media player built on Android’s low level media APIs. It’s designed to be easy to customize and extend, allowing many components to be replaced with custom implementations.

Advantages

ExoPlayer has a number of advantages over Android’s built in MediaPlayer:

  • Support for Dynamic Adaptive Streaming over HTTP (DASH) and SmoothStreaming, neither of which are are supported by MediaPlayer (it also supports HTTP Live Streaming (HLS), MP4, MP3, WebM, M4A, MPEG-TS and AAC).
  • Support for advanced HLS features, such as correct handling of #EXT-X-DISCONTINUITY tags.
  • The ability to customize and extend the player to suit your use case. ExoPlayer is designed specifically with this in mind, and allows many components to be replaced with custom implementations.
  • Easily update the player along with your application. Because ExoPlayer is a library that you include in your application apk, you have control over which version you use and you can easily update to a newer version as part of a regular application update.
  • Fewer device specific issues.

Adding ExoPlayer to an Android app

Because ExoPlayer is a library that you include in your application, it can be easily updated along with your app.

Note: ExoPlayer’s standard audio and video components rely on Android’s MediaCodec API, which was released in Android 4.1 (API level 16). Hence they do not work on earlier versions of Android.

Add ExoPlayer to Android Studio

The easiest way to get started using ExoPlayer is by including the following in your project’s build.gradle file:

compile 'com.google.android.exoplayer:exoplayer:r1.5.3'

I’m using release 1.5.3 but you can look at the release history to choose the one you want.

Creating an instance of the ExoPlayer:


private ExoPlayer exoPlayer;
...
@Override
public void onCreate() {
exoPlayer = ExoPlayer.Factory.newInstance(1);
}

Where 1 is the number of renderers to be used. In this case it’s 1 since we will only use an audio renderer. If you need audio and video renderers it should be 2.

Preparing the exoPlayer:

A TrackRenderer plays a specific type of media, such as video, audio or text. The ExoPlayer class invokes methods on its TrackRenderer instances from a single playback thread, and by doing so causes each type of media be rendered as the global playback position is advanced.

The ExoPlayer library provides MediaCodecAudioTrackRenderer for audio. This implementation makes use of Android’s MediaCodec class to decode individual media samples. They can handle all audio and video formats supported by a given Android device.


private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENT_COUNT = 256;
...
// String with the url of the radio you want to play
String url = getRadioUrl();
Uri radioUri = Uri.parse(url);
// Settings for exoPlayer
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
DataSource dataSource = new DefaultUriDataSource(context, null, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
radioUri, dataSource, allocator, BUFFER_SEGMENT_SIZE * BUFFER_SEGMENT_COUNT);
audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
// Prepare ExoPlayer
exoPlayer.prepare(audioRenderer);

 

The Exo Player library provides ExtractorSampleSource to play traditional media formats, including MP3, M4A, MP4, WebM, MPEG-TS and AAC.

Play and Stop Exo Player:

When all settings are ready, you must call setPlayWhenReady(true) method.
When you are done using it, call exoPlayer.stop() and don’t forget to release with exoPlayer.release()

Add ons:

Checking state:

In order to check the current state of the player we can use getPlayWhenReady() method.


if (exoPlayer != null && exoPlayer.getPlayWhenReady()) {
... do something when exo player is playing...
}

Muting/Unmuting Player:


public static void Mute() {
exoPlayer.sendMessage(audioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, 0f);
}

public static void Play() {
exoPlayer.sendMessage(audioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, 1f);
}

More add ons:

For more features for reproducing audio or video you can visit the ExoPlayer developer guide:

https://google.github.io/ExoPlayer/guide.html

Other resources:

For more information about this library you can take a look at the Google IO video of one of Google’s software engineers Oliver Woodman.