Android RecyclerView Multiple Layout Sealed Class

 Item.kt, the sealed class with two item types. A list of Items will be created but with different item types, either ItemOne or ItemTwo, but it will be a list of Items to be passed to the RecyclerView adapter.

sealed class Item()
class ItemOne(var name: String = "") : Item()
class ItemTwo(var name: String = "") : Item()

MainActivity.kt, the main class that creates the sample data for the recycerview.

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

import java.util.ArrayList

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initializing list view with the custom adapter
        val itemList = ArrayList<Item>()
        val itemArrayAdapter = ItemArrayAdapter(itemList)
        item_list.layoutManager = LinearLayoutManager(this)
        item_list.itemAnimator = DefaultItemAnimator()
        item_list.adapter = itemArrayAdapter

        // Populating list items
        for (i in 0..99) {
            if (i % 2 == 0) {
                itemList.add(ItemOne("Item " + i))
            } else {
                itemList.add(ItemTwo("Item " + i))
            }
        }
    }
}

ItemArrayAdapter.kt, in the getItemViewType, it checks the type of the items, and it is being used to determine which layout to use.

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

class ItemArrayAdapter(private val itemList: ArrayList<Item> = ArrayList()) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // get the size of the list
    override fun getItemCount(): Int = itemList.size

    // determine which layout to use for the row
    override fun getItemViewType(position: Int): Int {
        val item = itemList[position]
        return when (item) {
            is ItemOne -> TYPE_ONE
            is ItemTwo -> TYPE_TWO
        }
    }

    // specify the row layout file and click for each row
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == TYPE_ONE) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_type1, parent, false)
            return ViewHolderOne(view)
        } else if (viewType == TYPE_TWO) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_type2, parent, false)
            return ViewHolderTwo(view)
        } else {
            throw RuntimeException("The type has to be ONE or TWO")
        }
    }

    // load data in each row element
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, listPosition: Int) {
        when (holder.itemViewType) {
            TYPE_ONE -> initLayoutOne(holder as ViewHolderOne, listPosition)
            TYPE_TWO -> initLayoutTwo(holder as ViewHolderTwo, listPosition)
            else -> {
            }
        }
    }

    private fun initLayoutOne(holder: ViewHolderOne, pos: Int) {
        val item = itemList[pos] as ItemOne
        holder.item.text = item.name
    }

    private fun initLayoutTwo(holder: ViewHolderTwo, pos: Int) {
        val item = itemList[pos] as ItemTwo
        holder.tvLeft.text = item.name
        holder.tvRight.text = item.name
    }

    // Static inner class to initialize the views of rows
    internal class ViewHolderOne(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var item: TextView
        init {
            item = itemView.findViewById(R.id.row_item) as TextView
        }
    }

    inner class ViewHolderTwo(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvLeft: TextView
        var tvRight: TextView

        init {
            tvLeft = itemView.findViewById(R.id.row_item_left) as TextView
            tvRight = itemView.findViewById(R.id.row_item_right) as TextView
        }
    }

    companion object {
        private val TYPE_ONE = 1
        private val TYPE_TWO = 2
    }
}
<pre>

activity_main.xml
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.v7.widget.RecyclerView
        android:id="@+id/item_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
</LinearLayout>

list_item_type1.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    <TextView
        android:id="@+id/row_item"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Left"
        android:background="#10f1ed"/>
</LinearLayout>

list_item_type2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    <TextView
        android:id="@+id/row_item_left"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_weight="5"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Left"
        android:background="#c7da1b"/>
    <TextView
        android:id="@+id/row_item_right"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_weight="5"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Right"
        android:background="#36e6ae"/>
</LinearLayout>

Android Exoplayer v2 simple example

 The dependencies

implementation 'com.google.android.exoplayer:exoplayer-core:2.10.6'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.6'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.10.6'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.10.6'

The layout files

custom_playback_controller.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layoutDirection="ltr"
    android:background="#CC000000"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingTop="4dp"
        android:orientation="horizontal">

        <!--<ImageButton android:id="@id/exo_prev"-->
            <!--style="@style/ExoMediaButton.Previous"/>-->

        <ImageButton android:id="@id/exo_rew"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Rewind"/>

        <ImageButton android:id="@id/exo_shuffle"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Shuffle"/>

        <ImageButton android:id="@id/exo_repeat_toggle"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton"/>

        <ImageButton android:id="@id/exo_play"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Play"/>

        <ImageButton android:id="@id/exo_pause"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.Pause"/>

        <ImageButton android:id="@id/exo_ffwd"
            android:tint="#FF00A6FF"
            style="@style/ExoMediaButton.FastForward"/>

        <!--<ImageButton android:id="@id/exo_next"-->
            <!--style="@style/ExoMediaButton.Next"/>-->

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView android:id="@id/exo_position"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textStyle="bold"
            android:paddingLeft="4dp"
            android:paddingRight="4dp"
            android:includeFontPadding="false"
            android:textColor="#FF00A6FF"/>

        <com.google.android.exoplayer2.ui.DefaultTimeBar
            android:id="@id/exo_progress"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="26dp"/>

        <TextView android:id="@id/exo_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textStyle="bold"
            android:paddingLeft="4dp"
            android:paddingRight="4dp"
            android:includeFontPadding="false"
            android:textColor="#FF00A6FF"/>

    </LinearLayout>

</LinearLayout>

activity_player.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:background="#000000">
    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:controller_layout_id="@layout/custom_playback_control"/>
</FrameLayout>

The Kotlin class files

ComponentListener.kt

import android.util.Log

import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.analytics.AnalyticsListener
import com.google.android.exoplayer2.audio.AudioRendererEventListener
import com.google.android.exoplayer2.decoder.DecoderCounters
import com.google.android.exoplayer2.video.VideoRendererEventListener

class ComponentListener : Player.EventListener, VideoRendererEventListener, AudioRendererEventListener, AnalyticsListener {
    private val TAG = "ComponentListener"

    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        val stateString: String
        when (playbackState) {
            Player.STATE_IDLE -> stateString = "ExoPlayer.STATE_IDLE      -"
            Player.STATE_BUFFERING -> stateString = "ExoPlayer.STATE_BUFFERING -"
            Player.STATE_READY -> stateString = "ExoPlayer.STATE_READY     -"
            Player.STATE_ENDED -> stateString = "ExoPlayer.STATE_ENDED     -"
            else -> stateString = "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString playWhenReady: $playWhenReady")
    }

    // Implementing VideoRendererEventListener.

    override fun onVideoEnabled(counters: DecoderCounters) {
        // Do nothing.
    }

    override fun onVideoDecoderInitialized(decoderName: String, initializedTimestampMs: Long, initializationDurationMs: Long) {
        // Do nothing.
    }

    override fun onVideoInputFormatChanged(format: Format) {
        // Do nothing.
    }

    override fun onDroppedFrames(count: Int, elapsedMs: Long) {
        // Do nothing.
    }

    override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
        // Do nothing.
    }

    override fun onVideoDisabled(counters: DecoderCounters) {
        // Do nothing.
    }

    // Implementing AudioRendererEventListener.

    override fun onAudioEnabled(counters: DecoderCounters) {
        // Do nothing.
    }

    override fun onAudioSessionId(audioSessionId: Int) {
        // Do nothing.
    }

    override fun onAudioDecoderInitialized(decoderName: String, initializedTimestampMs: Long, initializationDurationMs: Long) {
        // Do nothing.
    }

    override fun onAudioInputFormatChanged(format: Format) {
        // Do nothing.
    }

    override fun onAudioSinkUnderrun(bufferSize: Int, bufferSizeMs: Long, elapsedSinceLastFeedMs: Long) {
        // Do nothing.
    }

    override fun onAudioDisabled(counters: DecoderCounters) {
        // Do nothing.
    }

}

PlayerActivity.kt

import android.annotation.SuppressLint
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.example.exoplayer.MainActivity.Companion.MEDIA_URI

import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioRendererEventListener
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.util.Util
import com.google.android.exoplayer2.video.VideoRendererEventListener

/**
 * A fullscreen activity to play audio or video streams.
 */
class PlayerActivity : AppCompatActivity() {

    companion object {
        private val BANDWIDTH_METER = DefaultBandwidthMeter()
    }

    private var playerView: PlayerView? = null
    private lateinit var player: SimpleExoPlayer

    private var mediaUri = ""

    private var playbackPosition: Long = 0
    private var currentWindow: Int = 0
    private var playWhenReady = true

    private var componentListener: ComponentListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_player)

        mediaUri = intent.getStringExtra(MEDIA_URI)

        if (mediaUri.isEmpty()) {
            AlertDialog.Builder(this)
                    .setTitle("Invalid media uri")
                    .setMessage("The uri is empty, please provide a valid media uri")
                    .setPositiveButton("OK") { _, _ ->
                        finish()
                    }
                    .show()
        }

        playerView = findViewById(R.id.video_view)
        componentListener = ComponentListener()
    }

    public override fun onStart() {
        super.onStart()
        if (Util.SDK_INT > 23) {
            initializePlayer(mediaUri)
        }
    }

    public override fun onResume() {
        super.onResume()
        hideSystemUi()
        if (Util.SDK_INT <= 23 || !::player.isInitialized) {
            initializePlayer(mediaUri)
        }
    }

    public override fun onPause() {
        super.onPause()
        if (Util.SDK_INT <= 23 && ::player.isInitialized) {
            releasePlayer()
        }
    }

    public override fun onStop() {
        super.onStop()
        if (Util.SDK_INT > 23 && ::player.isInitialized) {
            releasePlayer()
        }
    }

    private fun initializePlayer(uri: String) {
        if (uri.isEmpty()) {
            return
        }

        if (!::player.isInitialized) {
            // a factory to create an AdaptiveVideoTrackSelection
            val adaptiveTrackSelectionFactory = AdaptiveTrackSelection.Factory(BANDWIDTH_METER)

            player = ExoPlayerFactory.newSimpleInstance(
                    this,
                    DefaultRenderersFactory(this),
                    DefaultTrackSelector(adaptiveTrackSelectionFactory),
                    DefaultLoadControl())

            player.addListener(componentListener)
            player.addAnalyticsListener(componentListener)
            player.addAnalyticsListener(componentListener)
        }

        playerView?.player = player

        player.playWhenReady = playWhenReady
        player.seekTo(currentWindow, playbackPosition)

        val mediaSource = buildMediaSource(Uri.parse(uri))
        player.prepare(mediaSource, true, false)
    }

    private fun releasePlayer() {
        playbackPosition = player.currentPosition
        currentWindow = player.currentWindowIndex
        playWhenReady = player.playWhenReady
        player.removeListener(componentListener)
        player.setVideoListener(null)
        player.removeAnalyticsListener(componentListener)
        player.removeAnalyticsListener(componentListener)
        player.release()
    }

    private fun buildMediaSource(uri: Uri): MediaSource {

        val userAgent = "exoplayer-codelab"

        if (uri.getLastPathSegment().contains("mp3") || uri.getLastPathSegment().contains("mp4")) {
            return ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
                    .createMediaSource(uri)
        } else if (uri.getLastPathSegment().contains("m3u8")) {
            return HlsMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
                    .createMediaSource(uri)
        } else {
            val dashChunkSourceFactory = DefaultDashChunkSource.Factory(
                    DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER))
            val manifestDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
            return DashMediaSource.Factory(dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(uri)
        }
    }

    @SuppressLint("InlinedApi")
    private fun hideSystemUi() {
        playerView!!.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
    }

}

The resource files
colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="colorPrimary">#FFFFFF</color>
  <color name="colorPrimaryDark">#000000</color>
  <color name="colorAccent">#FF4081</color>
</resources>

strings.xml

<resources>
  <string name="app_name">ExoPlayer code lab</string>
  <string name="player_activity_name">Media player</string>
  <string name="media_url_mp3">http://storage.googleapis.com/exoplayer-test-media-0/play.mp3</string>
  <string name="media_url_mp4">http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4</string>
  <string name="media_url_dash"><![CDATA[http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0]]></string>
  <string name="logo">Google logo</string>
</resources>

styles.xml

<resources>

  <style name="PlayerTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowBackground">@null</item>
  </style>

</resources>

Player activity class field variables, these class field variables are used within the player class.

private var playerView: PlayerView? = null
private var player: SimpleExoPlayer? = null

private var playbackPosition: Long = 0
private var currentWindow: Int = 0
private var playWhenReady = true
private var componentListener: ComponentListener? = null

Initialize the playerView and componentListener in the onCreate function.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_player)

    playerView = findViewById(R.id.video_view)

    componentListener = ComponentListener()
}

The function for initializing the Exoplayer

    private fun initializePlayer(uri: String) {
        if (uri.isEmpty()) {
            return
        }

        if (!::player.isInitialized) {
            // a factory to create an AdaptiveVideoTrackSelection
            val adaptiveTrackSelectionFactory = AdaptiveTrackSelection.Factory(BANDWIDTH_METER)

            player = ExoPlayerFactory.newSimpleInstance(
                    this,
                    DefaultRenderersFactory(this),
                    DefaultTrackSelector(adaptiveTrackSelectionFactory),
                    DefaultLoadControl())

            player.addListener(componentListener)
            player.addAnalyticsListener(componentListener)
            player.addAnalyticsListener(componentListener)
        }

        playerView?.player = player

        player.playWhenReady = playWhenReady
        player.seekTo(currentWindow, playbackPosition)

        val mediaSource = buildMediaSource(Uri.parse(uri))
        player.prepare(mediaSource, true, false)
    }

The function for releasing the Exoplayer.

    private fun releasePlayer() {
        playbackPosition = player.currentPosition
        currentWindow = player.currentWindowIndex
        playWhenReady = player.playWhenReady
        player.removeListener(componentListener)
        player.setVideoListener(null)
        player.removeAnalyticsListener(componentListener)
        player.removeAnalyticsListener(componentListener)
        player.release()
    }

The function for building the media source to be played in the Exoplayer.

private fun buildMediaSource(uri: Uri): MediaSource {

    val userAgent = "exoplayer-codelab"

    if (uri.getLastPathSegment().contains("mp3") || uri.getLastPathSegment().contains("mp4")) {
        return ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
                .createMediaSource(uri)
    } else if (uri.getLastPathSegment().contains("m3u8")) {
        return HlsMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent))
                .createMediaSource(uri)
    } else {
        val dashChunkSourceFactory = DefaultDashChunkSource.Factory(
                DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER))
        val manifestDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
        return DashMediaSource.Factory(dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(uri)
    }
} 

The activity life cycle functions (onStart and onResume) that calls the function for initializing the exoplayer.

public override fun onStart() {
    super.onStart()
    if (Util.SDK_INT > 23) {
        initializePlayer(mediaUri)
    }
}

public override fun onResume() {
    super.onResume()
    hideSystemUi()
    if (Util.SDK_INT <= 23 || !::player.isInitialized) {
        initializePlayer(mediaUri)
    }
}

The activity life cycle functions (onPause and onStop) that calls the function for releasing the exoplayer.

public override fun onPause() {
    super.onPause()
    if (Util.SDK_INT <= 23 && ::player.isInitialized) {
        releasePlayer()
    }
}

public override fun onStop() {
    super.onStop()
    if (Util.SDK_INT > 23 && ::player.isInitialized) {
        releasePlayer()
    }
}

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...