37

CardView usually used to decorate exactly one element. But sometimes you need to wrap into this widget several items. Like in Inbox app, for example.

enter image description here

So what is the best way to do this? It can be implemented via custom LayoutManager or even custom ItemDecoration. Implementation of custom LayoutManager is not an easy task(with full support of animations, item decorations, etc). In the second option, the drawing of the boundaries must be implemented manually, ignoring CardView(and Android-L elevation) implementation.

bvitaliyg
  • 3,155
  • 4
  • 30
  • 46

1 Answers1

39

TL;DR:

It's not one CardView which hosts elements, it's several successive CardViews with different margins:

For the top CardView in group:

    android:layout_marginTop="5dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="0dp"
    card_view:cardCornerRadius="0dp"

For the bottom CardView in group:

    android:layout_marginTop="0dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="5dp"
    card_view:cardCornerRadius="0dp"

And the middle one, as set margins Top&Bottom to 0:

    android:layout_marginTop="0dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="0dp"
    card_view:cardCornerRadius="0dp"

About Inbox app:

This is hierarchy of the app (of course, simplified a bit):

|android.support.v4.widget.DrawerLayout
---|FrameLayout
-------|android.support.v7.widget.RecyclerView
-------|android.support.v7.widget.Toolbar
---|android.support.design.widget.NavigationView

The full structure even without navigation drawer & collapsed cards looks like: enter image description here

The interesting part starts, when you dive into the RecyclerView's items structure.
There're 2 types of items Google uses - the separators (with date and actions on the right) and the cards. Even though cards have different content inside, from the ViewHolder perspective - RecyclerView has 2 types of items)

  1. Separator enter image description here

    This one is just a LinearLayout with TextView and ImageView inside:

    enter image description here

  2. Item Card
    enter image description here

    Its layout adjusts based on the content being bind to the ViewHolder For example, simple email like the one in focus is a CardView with nested ImageView and 3 TextViews:

    enter image description here

So the only question left, how do Google-guys "merge" cards into one big card and avoid extra shadows.

The trick is really simple:

  1. All CardView's have card_view:cardCornerRadius="0dp"
  2. Top CardView of the group have margin set 5dp for top/left/right, but 0dp for the bottom:

    android:layout_marginTop="5dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="0dp"
    
  3. Bottom CardView of the group have margin set 5dp for left/right/bottom, but 0dp for the top:

    android:layout_marginTop="0dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="5dp"
    
  4. Middle CardView of the group have margin set 5dp for left/right, but 0dp for the top/bottom:

    android:layout_marginTop="0dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp"
    android:layout_marginBottom="0dp"
    

That's it!

Here's a small example I've wrote:

enter image description here

The layout (with tricky margins)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    xmlns:card_view="http://schemas.android.com/apk/res-auto">
    <android.support.v7.widget.CardView
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginBottom="0dp"
        card_view:cardCornerRadius="0dp"
        card_view:contentPadding="10dp">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="card1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold"/>
        </FrameLayout>
    </android.support.v7.widget.CardView>
    <android.support.v7.widget.CardView
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="0dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginBottom="0dp"
        card_view:cardCornerRadius="0dp"
        card_view:contentPadding="10dp">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="card2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold"/>
        </FrameLayout>
    </android.support.v7.widget.CardView>
    <android.support.v7.widget.CardView
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="0dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginBottom="5dp"
        card_view:cardCornerRadius="0dp"
        card_view:contentPadding="10dp">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="card3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold"/>
        </FrameLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

I hope, it helps

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • as I understand this approach leads to overdraws. at first it draws all shadows of top element, than Recycler adds next card view that hides shadow partially. – Andrew Matiuk Jan 10 '16 at 11:48
  • @veon well, that's what Inbox do. It's not exactly overdrawing, but there's a tiny overlap of shadows on the left&right at the junction of CardViews. – Konstantin Loginov Jan 10 '16 at 11:51
  • also as I understand it's not possible to draw ripple (click) animation on big card – Andrew Matiuk Jan 10 '16 at 11:52
  • @veon yep, the truth is that there's **no** big card. There're several successive CardViews. So ripple animation applies only to each of these successive cards, but not to all of them at once. – Konstantin Loginov Jan 10 '16 at 11:53
  • do you have ideas how to make it with one card? – Andrew Matiuk Jan 10 '16 at 20:57
  • @veon once Google is doing this - I think it's right to do the same. Do you want to put a unknown size list inside CardView? – Konstantin Loginov Jan 10 '16 at 20:59
  • yes I have a list divided on groups, on Android 2.x it was two list activities : select group, preview content (icons+text) and button "go" in action bar. now I want to use Material Cards for groups. Size of the inner list is unknown, but it's not "that big", so it should fit users screen height. – Andrew Matiuk Jan 10 '16 at 21:26
  • 3
    @veon the proper way is to do this like written above - one recyclerview, several types of ViewHolders, successive CardViews "fake" group behaviour. The dirty solution - to put a LinearLayout into the CardView and dynamically create rows in it. It's bad because your gigantic CardView would be recycled all at once, if it's too big - it might cause OutOfMemory, etc. I.e. you're killing all the point of _RecyclerView_. But if your list is **moderately** big - you can try and see, if it works for you. – Konstantin Loginov Jan 10 '16 at 21:35
  • 2
    In Android 4.4.4 this does not work as the cards are layouted with space in-between: https://i.stack.imgur.com/pDWzl.png I read here: http://stackoverflow.com/questions/27599603/cardview-not-showing-shadow-in-android-l "the content area in a CardView take different sizes on pre-lollipop and lollipop devices" – PhilLab Nov 28 '16 at 13:39
  • @PhilLab have you found the solution? – thekekc Apr 30 '17 at 15:28
  • 3
    @thekekc try adding negative value margins, it worked for me. android:layout_marginBottom="-5dp" – Silvia H Aug 17 '17 at 11:32
  • You are statically adding all cards in single layout. How to inflate using recycler view ? Have to create separate item layout and then inflate based on number of items. It may be possible that there is/are only one, two or more than two items. – Smeet Dec 11 '18 at 07:01