26

What is the best strategy to achieve this feature:

enter image description here

I Have a horizontal RecyclerView with cards. Each card will fulfil the entire screen, but I want it to show part of the next card and previous one if it has more than one item.

I know I can achieve this by setting my card android:layout_width at the adapter to have a specific DP like 250dp instead of match_parent. But it doesn't look like a proper solution.

This is my code:

Activity with RecyclerView:

    class ListPokemon : AppCompatActivity() {

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

        val items = createListPokemons()
        recyclerView.adapter = PokemonAdapter(items)
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        recyclerView.setHasFixedSize(true)
        val pagerSnapHelper = PagerSnapHelper()
        pagerSnapHelper.attachToRecyclerView(recyclerView)
    }

    private fun createListPokemons(): List<Pokemon> {
        val pokemons = ArrayList<Pokemon>()
        pokemons += createPokemon("Pikachu")
        pokemons += createPokemon("Bulbasaur")
        pokemons += createPokemon("Charmander")
        pokemons += createPokemon("Squirtle")

        return pokemons
    }

    private fun createPokemon(name: String) = Pokemon(name = name, height = 1, weight = 69, id = 1)
}

Layout of Activity:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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="wrap_content"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>

</android.support.constraint.ConstraintLayout>

Adapter:

class PokemonAdapter(val list: List<Pokemon>) : RecyclerView.Adapter<PokemonAdapter.PokemonVH>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonAdapter.PokemonVH {
        return PokemonVH(LayoutInflater.from(parent.context)
            .inflate(R.layout.pokemon_item, parent, false))
    }

    override fun onBindViewHolder(holder: PokemonAdapter.PokemonVH, position: Int) {
        holder.textViewName.text = list[position].name
    }

    override fun getItemCount(): Int {
        return list.size
    }

    class PokemonVH(itemView: View) : RecyclerView.ViewHolder(itemView) {

        var textViewName: TextView = itemView.findViewById(R.id.textViewName)
    }
}

Layout of Adapter:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    android:layout_gravity="center_horizontal"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginEnd="16dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:padding="36dp"
            android:id="@+id/textViewName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="22sp"
            tools:text="Teste String"/>

    </LinearLayout>

</android.support.v7.widget.CardView>

This is my result:

enter image description here

I would like to show part of the next card at this situation. How can I do this?

Thanks.

leonvian
  • 4,719
  • 2
  • 26
  • 31

3 Answers3

79

What you need to do is set padding to your RecyclerView, set clipToPadding to false, use a SnapHelper with it, and you need to make sure the margins on your cards are less than or equal to the padding in the RecylerView.

So, let's say you want the distance from the cards to the sides of the screen to be 16dp and you want the distance between the cards to be 8dp. You'll have to set the margins on each card to 4dp, so the total margin is 8dp. And you have to set the padding to 12dp, given there's already a margin of 4dp on each side of the card.

It'll look a bit like this:

Your list:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager"
    android:clipToPadding="false"
    android:orientation="horizontal"
    android:paddingStart="12dp"
    android:paddingEnd="12dp"/>

Your cards:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginEnd="4dp"
    android:layout_marginStart="4dp"
    app:cardElevation="2dp"/>
Rodolfo
  • 946
  • 6
  • 4
  • Looks great! Thanks. – leonvian Jul 23 '18 at 16:39
  • What if we need to have variable margins depending on card position, So lets say we can see more of the card next to the first card (so it wont be centered) and rest of the intermediate cards are centered i.e. see equal amount of the cards before and after them and the last card also seems more of the penultimate card? – RamPrasadBismil May 23 '19 at 00:27
  • 1
    @RamPrasadBismil You have to set the margin in onBindViewHolder dynamically based on the position – Manos Mar 24 '20 at 09:46
  • 2
    What a clever trick! Though I don't understand why `SnapHelper` was mentioned, it's not being used in this solution. – Kirill Starostin Jul 14 '21 at 13:13
  • Works Perfect. Thanks.. Easy solution – Satyam Gondhale Aug 24 '22 at 11:58
1

I think the padding solution is not a good for all cases, because forces the last item to have padding to the right.

Personally i use runtime width calculation of each item and i am very satisfied with this. So you can do the following:

onBindViewHolder

if (position == data.size - 1) {
    holder.itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
} else {
    if (width == null) {
        holder.itemView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
              override fun onGlobalLayout() {
                   holder.itemView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                   width = holder.itemView.width
                   params.width = width!! - partOfPage
                   holder.itemView.requestLayout()
              }
         })
     } else {
         params.width = width!! - partOfPage
         holder.itemView.requestLayout()
     }
 }

The outcome is that all middle items are rendered showing a part of the next page, but the last one is rendered full width.

mspapant
  • 1,860
  • 1
  • 22
  • 31
-1

Change your CardView width from "match_parent" to "0dp". And add, layout_weight as "80" (or similar). Make your parent view (RecyclerView) layout_weightSum as "100".

android:layout_width="0dp"
android:layout_weight="80"
Gokul Nath KP
  • 15,485
  • 24
  • 88
  • 126