Skip to content

Latest commit

 

History

History
917 lines (663 loc) · 31.5 KB

CARDLIST.md

File metadata and controls

917 lines (663 loc) · 31.5 KB

Cards Library: CardList

In this page you can find info about:

Creating a base CardList

Creating a CardListView is pretty simple.

First, you need an XML layout that will display the CardListView.

    <it.gmariotti.cardslib.library.view.CardListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/myList"/>

Then create an Array of Cards:

       ArrayList<Card> cards = new ArrayList<Card>();

      //Create a Card
      Card card = new Card(getContext());

      //Create a CardHeader
      CardHeader header = new CardHeader(getContext());
      ....
      //Add Header to card
      card.addCardHeader(header);

      cards.add(card);

Last create a CardArrayAdapter, get a reference to the CardListView from your code and set your adapter.

        CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);

        CardListView listView = (CardListView) getActivity().findViewById(R.id.myList);
        if (listView!=null){
            listView.setAdapter(mCardArrayAdapter);
        }

This CardListView uses for each row the row-list layout res/layout/native_list_card_layout.xml.

Use your custom layout for each row

Card Library provides 2 built-in row-list layouts.

For the CardViewNative:

For the CardView:

You can customize the layout used for each item in ListView using the attr: card:list_card_layout_resourceID="@layout/my_layout

    <it.gmariotti.cardslib.library.view.CardListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/carddemo_list_gplaycard"
        card:list_card_layout_resourceID="@layout/native_list_card_thumbnail_layout" />

In your row-list layout you can use your CardViewNative with all its features and its possibilities. Example native_list_card_thumbnail_layout.xml:

    <it.gmariotti.cardslib.library.view.CardViewNative
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card="http://schemas.android.com/apk/res-auto"
        android:id="@+id/list_cardId"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/native_list_card.thumbnail"
        card:card_layout_resourceID="@layout/native_card_thumbnail_layout"
        />

You can build your layout, but need to have:

  1. a CardViewNative with the ID list_cardId

Screen

Cards with different inner layouts

If you want to use cards with different inner layouts you have to:

  1. set the number of different cards in your adapter with mCardArrayAdapter.setInnerViewTypeCount
    // Provide a custom adapter.
    // It is important to set the viewTypeCount
    // You have to provide in your card the type value with {@link Card#setType(int)} method.
    CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);
    mCardArrayAdapter.setInnerViewTypeCount(3);
  1. set the type inside your cards with card.setType
    MyCard card2= new MyCard(getActivity());
    card2.setType(2); //Very important with different inner layout

You can also override the setType method in your Card.

    public class CardExample extends Card{
        @Override
        public int getType() {
            //Very important with different inner layouts
            return 2;
        }
    }

Moreover you can extend CardArrayAdapter and provide your logic.

    /**
     *  With multiple inner layouts you have to set the viewTypeCount with {@link CardArrayAdapter#setInnerViewTypeCount(int)}.
     *  </p>
     *  An alternative is to provide your CardArrayAdapter  where you have to override this method:
     *  </p>
     *  public int getViewTypeCount() {}
     *  </p>
     *  You have to provide in your card the type value with {@link Card#setType(int)} method.
     *
     */
    public class MyCardArrayAdapter extends CardArrayAdapter{

        /**
         * Constructor
         *
         * @param context The current context.
         * @param cards   The cards to represent in the ListView.
         */
        public MyCardArrayAdapter(Context context, List<Card> cards) {
            super(context, cards);
        }

        @Override
        public int getViewTypeCount() {
            return 3;
        }
    }

You can see the example in 'ListDifferentInnerBaseFragment'.

Screen

Swipe and DismissableManager

If you want to enable the swipe action, you can customize the card swipe direction.

Cards in a CardListView can be swiped left, right or both. The default, if not set, is both.

To change the default behaviour you can set:

     mCardArrayAdapter.setDismissable(new RightDismissableManager());

     public class RightDismissableManager extends DefaultDismissableManager{

        @Override
        public SwipeDirection getSwipeDirectionAllowed() {
            return SwipeDirection.RIGHT; 
        }
    }

Swipe and Undo in CardListView

If you want to enable the swipe action with an Undo Action you have to:

  1. enable the swipe action on the single Cards
        //Create a Card
        Card card = new CustomCard(getActivity());

        //Enable a swipe action
        card.setSwipeable(true);
  1. provide a id for each card
        card.setId("xxxx");
  1. enable the undo action on the List
        CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);

        //Enable undo controller!
        mCardArrayAdapter.setEnableUndo(true);
  1. include the undo bar in your layout. You can use the build-in layout `res/layout/list_card_undo_message.xml'.
  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
               xmlns:card="http://schemas.android.com/apk/res-auto"
               android:layout_width="match_parent"
               android:layout_height="match_parent">

      <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent">

          <!-- You can customize this layout.
           You need to have in your layout a `CardView` with the ID `list_cardId` -->
          <it.gmariotti.cardslib.library.view.CardListView
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:id="@+id/carddemo_list_gplaycard"
              card:list_card_layout_resourceID="@layout/native_list_card_thumbnail_layout"/>

      </RelativeLayout>

      <!-- Include undo message layout -->
      <include layout="@layout/list_card_undo_message"/>

  </FrameLayout>

The library provides also these undo layouts (check the images below):

It is not mandatory. You can set a Card.OnSwipeListener to listen the swipe action.

        //You can set a SwipeListener.
        card.setOnSwipeListener(new Card.OnSwipeListener() {
            @Override
            public void onSwipe(Card card) {
                //Do something
            }
        });

Then you can set a Card.OnUndoSwipeListListener to listen the undo action.

            card.setOnUndoSwipeListListener(new OnUndoSwipeListListener() {
                @Override
                public void onUndoSwipe(Card card) {
                    //Do something
                }
            });

There is also a callback to listener when the Undo controller hides the Undo Bar.

            card.setOnUndoHideSwipeListListener(new OnUndoHideSwipeListListener() {
                @Override
                public void onUndoHideSwipe(Card card) {
                    //Do something
                }
            });

You can customize the undo bar. The easiest way is to copy the styles inside res/values/styles_undo.xml in your project.

You can see the example in ListGplayUndoCardFragment.

You can customize the distance from the edge needed to activate the swipe action, overriding this value in your integers.xml

    <integer name="list_card_swipe_distance_divisor">2</integer>

The default value is 2 = half card.

Screen

list_card_undo_material_message list_card_undo_materialmobile_message
Screen Screen

If you are using a material undobar you can customize these colors overriding them in your project:

    <color name="card_undobar_material_text_color">#4CAF50</color>
    <color name="card_undobar_material_background_color">#323232</color>

Swipe and Undo with a custom UndoBar

You can provide a custom UndoBar.

This UndoBar has to contains these elements:

  1. A TextView

  2. A Button

  3. A root element with an id attribute

You should use the same Ids provided in the default layout list_card_undo_message, but if you have to use different ids you can use the CardArrayAdapter.setUndoBarUIElements:

Example:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/my_undobar"
        style="@style/list_card_UndoBar"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/my_undobar_message"
            style="@style/list_card_UndoBarMessage"/>

        <Button
            android:id="@+id/my_undobar_button"
            style="@style/list_card_UndoBarButton"/>

    </LinearLayout>
      CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);

      //It is very important to set the UndoBarUIElements before to call the setEnableUndo(true);
      mCardArrayAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements() {
                  @Override
                  public int getUndoBarId() {
                      return R.id.myid_undobar;
                  }

                  @Override
                  public int getUndoBarMessageId() {
                      return R.id.my_undobar_message;
                  }

                  @Override
                  public int getUndoBarButtonId() {
                      return R.id.my_undobar_button;
                  }
              });
      mCardArrayAdapter.setEnableUndo(true);

      if (mListView!=null){
          mListView.setAdapter(mCardArrayAdapter);
      }

If you would like to use more ListViews in the same screen, you have to use the code above.

Also you can customize your Undobar message.

You can clone in your res/values/strings.xml these strings and override them:

    <!-- Undo Controller-->

    <string name="list_card_undo_title">Undo</string>
    <!--<string name="undo_card">Card removed</string>-->

    <plurals name="list_card_undo_items">
        <item quantity="one">1 card removed</item>
        <item quantity="other">%d cards removed</item>
    </plurals>

Otherwise you can override the method getMessageUndo in your UndoBarController.DefaultUndoBarUIElements.

    //It is very important to set the UndoBarUIElements before to call the setEnableUndo(true);
    mCardArrayAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements(){

            @Override
            public String getMessageUndo(CardArrayAdapter cardArrayAdapter, String[] itemIds, int[] itemPositions) {

                //It is only an example
                //Pay attention: you can't find the cards with these positions in your arrayAdapter because the cards are removed.
                //You have to/can use your id itemIds, to identify your cards.
                
                StringBuffer message=new StringBuffer();
                for (int id:itemIds){
                    Card card =myIdsList.get(id);    
                    message.append(card.getTitle());
                }
                return message.toString();
            }
        });

You can also customize the animation of the UndoBar and you can enable a swipe action over it to swipe the undobar off the screen. The default undobar has an alpha animation and no swipe action enabled.

  mCardArrayAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements(){
  
              @Override
              public SwipeDirectionEnabled isEnabledUndoBarSwipeAction() {
                  return SwipeDirectionEnabled.TOPBOTTOM;   // it enables a swipe action from TOP to BOTTOM
                  //return SwipeDirectionEnabled.LEFTRIGHT; // it enables a swipe action FROM LEFT RO RIGHT
                  //return SwipeDirectionEnabled.NONEM      // it is the default. No action.
              }
  
              @Override
              public AnimationType getAnimationType() {
                  return AnimationType.TOPBOTTOM;  // Translation from bottom to top
                  //return AnimationType.ALPHA;    // Default: alpha animator
              }
          });

Migration from 1.6.0 (and previous releases) - to 1.7.0

The 1.7.0 may introduce a breaking change with UndoBar and UndoBarUIElements. To migrate your code you have to:

  • rename UndoBarController.UndoBarUIElements() in UndoBarController.DefaultUndoBarUIElements() if you are using it.
  • extend UndoBarController.DefaultUndoBarUIElements() instead of implementing the UndoBarController.UndoBarUIElements() interface if you are using a custom undo bar.

Swipe and custom OnScrollListener

The CardListView of cards with swipe action uses a own SwipeOnScrollListener to ensure that the SwipeDismissListViewTouchListener is paused during list view scrolling.

If you want to use your custom OnScrollListener you can use this code:

 listView.setOnScrollListener(

     new SwipeOnScrollListener() {
          @Override
          public void onScrollStateChanged(AbsListView view, int scrollState) {
              //It is very important to call the super method here to preserve built-in functions
              super.onScrollStateChanged(view,scrollState);
          }

         @Override
         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
             //Do something...
         }
     });

It is very important to call the super.onScrollStateChanged(view,scrollState); in onScrollStateChanged method to preserve the built-in functions in swipe actions.

You can use a default AbsListView.OnScrollListener if you are working with a list of cards without swipe action.

How to use an external adapter

Some libraries use a own adapter as ListViewAnimations

In this case you can use this code:

         mListView.setExternalAdapter(ownAdapter,mCardArrayAdapter);

Pay attention. You can use this method, if your ownAdapter calls the mCardArrayAdapter#getView() method.

Using a cursor adapter

Creating a CardListView is pretty simple.

First, you need an XML layout that will display the CardListView.

    <it.gmariotti.cardslib.library.view.CardListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/myList"/>

Then create your CardCursorAdapter.

You have to extend the CardCursorAdapter and override the getCardFromCursor method.

      public class MyCursorCardAdapter extends CardCursorAdapter {

          public MyCursorCardAdapter(Context context) {
              super(context);
          }

          @Override
          protected Card getCardFromCursor(Cursor cursor) {
              MyCursorCard card = new MyCursorCard(super.getContext());
              setCardFromCursor(card,cursor);

              //Create a CardHeader
              CardHeader header = new CardHeader(super.getContext());

              //Set the header title
              header.setTitle(card.mainHeader);

              //Add Header to card
              card.addCardHeader(header);

              return card;
          }

          private void setCardFromCursor(MyCursorCard card,Cursor cursor) {

              card.mainTitle=cursor.getString(CardCursorContract.CardCursor.IndexColumns.TITLE_COLUMN);
              card.secondaryTitle=cursor.getString(CardCursorContract.CardCursor.IndexColumns.SUBTITLE_COLUMN);
              card.mainHeader=cursor.getString(CardCursorContract.CardCursor.IndexColumns.HEADER_COLUMN);
              card.setId(""+cursor.getInt(CardCursorContract.CardCursor.IndexColumns.ID_COLUMN));
          }
      }

Last create your MyCursorCardAdapter instance, get a reference to the CardListView from your code and set your adapter.

        MyCursorCardAdapter mAdapter = new MyCursorCardAdapter(getActivity());

        CardListView mListView = (CardListView) getActivity().findViewById(R.id.carddemo_list_cursor);
        if (mListView != null) {
            mListView.setAdapter(mAdapter);
        }

With the this type of cursor, currently you can't use the swipe and undo actions.

Using a CardList in MultiChoiceMode

If you would like to have a CardList with a MultiChoiceMode built-in feature you can use a CardArrayMultiChoiceAdapter.

This class extends CardArrayAdapter and preserves all its features.

First of all you have to create your CardArrayMultiChoiceAdapter extending the base class and implementing the missing methods.

    public class MyCardArrayMultiChoiceAdapter extends CardArrayMultiChoiceAdapter {

            public MyCardArrayMultiChoiceAdapter(Context context, List<Card> cards) {
                super(context, cards);
            }

            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                //It is very important to call the super method
                super.onCreateActionMode(mode, menu);

                mActionMode=mode; // to manage mode in your Fragment/Activity

                //If you would like to inflate your menu
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.carddemo_multichoice, menu);

                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                if (item.getItemId() == R.id.menu_share) {
                    Toast.makeText(getContext(), "Share;" + formatCheckedCard(), Toast.LENGTH_SHORT).show();
                    return true;
                }
                if (item.getItemId() == R.id.menu_discard) {
                    discardSelectedItems(mode);
                    return true;
                }
                return false;
            }

            @Override
            public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked, CardView cardView, Card card) {
                Toast.makeText(getContext(), "Click;" + position + " - " + checked, Toast.LENGTH_SHORT).show();
            }

            private void discardSelectedItems(ActionMode mode) {
                ArrayList<Card> items = getSelectedCards();
                for (Card item : items) {
                    remove(item);
                }
                mode.finish();
            }


            private String formatCheckedCard() {

                SparseBooleanArray checked = mCardListView.getCheckedItemPositions();
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < checked.size(); i++) {
                    if (checked.valueAt(i) == true) {
                        sb.append("\nPosition=" + checked.keyAt(i));
                    }
                }
                return sb.toString();
            }

        }

It is very important, if you override the onCreateActionMode() method, to call the super.onCreateActionMode().

Then you have to implement this onLongClickListener in your cards:

       card.setOnLongClickListener(new Card.OnLongCardClickListener() {
            @Override
            public boolean onLongClick(Card card, View view) {
                return mCardArrayAdapter.startActionMode(getActivity());

            }
       });

Finally get a reference to the CardListView from your code and set your adapter.

        MyCardArrayMultiChoiceAdapter mCardGridArrayAdapter = new MyCardArrayMultiChoiceAdapter(getActivity(), cards);

        CardListView listView = (CardListView) getActivity().findViewById(R.id.myGrid);
        if (listView!=null){
             listView.setAdapter(mCardGridArrayAdapter);
             listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
        }

When an item is clicked and the action mode was already active, changes the selection state of the clicked item, just as if it had been long clicked.

As you can see in code above use ArrayList<Card> items = getSelectedCards(); to get the selected cards.

You need to implement the onItemCheckedStateChanged method if you would like to handle the click on a Card.

If you would like to customize the sentence "item selected", you can do it in your project overriding these strings in res/values-XX/strings.xml.

    <!-- Card selected item with CAB -->
    <plurals name="card_selected_items">
        <item quantity="one">1 item selected</item>
        <item quantity="other">%d items selected</item>
    </plurals>

You can see an example in ListGplayCardCABFragment (source).

Using a CardList in MultiChoiceMode and CursorAdapter

If you would like to have a CardList with a CursorAdapter and a MultiChoiceMode built-in feature you can use a CardCursorMultiChoiceAdapter.

This class extends CardCursorAdapter and preserves all its features.

All considerations, written above, are valid.

    public class MyCardCursorMultiChoiceAdapter extends CardCursorMultiChoiceAdapter {

           public MyCardCursorMultiChoiceAdapter(Context context) {
               super(context);
           }


           @Override
           protected Card getCardFromCursor(Cursor cursor) {

                MyCursorCard card = new MyCursorCard(super.getContext());

                //Implement you code here

                //It is very important.
                //You have to implement this onLongClickListener in your cards to enable the multiChoice
                card.setOnLongClickListener(new Card.OnLongCardClickListener() {
                    @Override
                    public boolean onLongClick(Card card, View view) {
                        return startActionMode(getActivity());
                    }
                });
           }

    }

You can see an example in ListGplayCursorCardCABFragment (source).

SectionedCardList

The SectionedCardAdapter allow to display a CardList with Sections.

It doesn't extend the CardArrayAdapter, it works with a CardArrayAdapter.

Screen

You can use it with our CardArrayAdapter without changing your code:

        //Standard array
        CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);

        // Define your sections
        List<GplayCardSection> sections =  new ArrayList<GplayCardSection>();
        sections.add(new CardSection(1,"Section 1"));
        sections.add(new CardSection(3,"Section 2"));
        GplayCardSection[] dummy = new GplayCardSection[sections.size()];

        //Define your Sectioned adapter
        SectionedCardAdapter mAdapter = new SectionedCardAdapter(getActivity(), mCardArrayAdapter);
        mAdapter.setCardSections(sections.toArray(dummy));

        CardListView listView = (CardListView) getActivity().findViewById(R.id.carddemo_list_gplaycard);
        if (listView!=null){
            //Use the external adapter.
            listView.setExternalAdapter(mAdapter,mCardArrayAdapter);
        }

You have to follow these steps:

  1. Define your sections using the CardSection class.
  2. Define your SectionedCardAdapter
  3. Set the externalAdapter to your CardListView

This code uses a default layout for the sections res/layout/base_section_layout

Using the code new CardSection(1,"Section 1")) you define the position (in the original array) where the section will be inserted and a title to be displayed.

It doesn't change the positions in the original array!

You can customize your CardSection using your favorite layout.

It is very easy o obtain it you have to extend the SectionedCardAdapter:

  1. defining the layout to be used in the constructor and
  2. overriding the getSectionView method
    /**
     * Sectioned adapter
     */
    public class MySectionedAdapter extends SectionedCardAdapter {

        /*
         * Define your layout in the constructor
         */
        public MySectionedAdapter(Context context, CardArrayAdapter cardArrayAdapter) {
            super(context, R.layout.carddemo_gplay_section_layout,cardArrayAdapter);
        }

        /*
         * Override this method to customize your layout
         */
        @Override
        protected View getSectionView(int position, View view, ViewGroup parent) {

            //Override this method to customize your section's view

            //Get the section
            MyCardSection section = (MyCardSection) getCardSections().get(position);

            if (section != null ) {
                //Set the title
                TextView title = (TextView) view.findViewById(R.id.carddemo_section_gplay_title);
                if (title != null)
                    title.setText(section.getTitle());

                //Set the button
                TextView buttonMore = (TextView) view.findViewById(R.id.carddemo_section_gplay_textmore);
                if (buttonMore != null)
                    buttonMore.setText(section.mButtonTxt);
            }

            return view;
        }
    }

And then use it with the same code described above.

        //Standard array
        CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);

        // Define your sections
        List<MyCardSection> sections = new ArrayList<MyCardSection>();
        sections.add(new MyCardSection(1,"Section 1","More"));
        sections.add(new MyCardSection(3,"Section 2","Other"));

        MyCardSection[] dummy = new MyCardSection[sections.size()];

        //Define your Sectioned adapter
        MySectionedAdapter mAdapter = new MySectionedAdapter(getActivity(), mCardArrayAdapter);
        mAdapter.setCardSections(sections.toArray(dummy));

        CardListView listView = (CardListView) getActivity().findViewById(R.id.carddemo_list_gplaycard);
        if (listView!=null){
            //Use the external adapter.
            listView.setExternalAdapter(mAdapter,mCardArrayAdapter);
        }

You can extend the CardSection (it is not mandatory) to customize your section model.

    /*
     * Custom Card section
     */
    public class GplayCardSection extends CardSection {

        public CharSequence mButtonTxt;

        public GplayCardSection(int firstPosition, CharSequence title, CharSequence buttonText) {
            super(firstPosition, title);
            mButtonTxt = buttonText;
        }
    }

You can update dinamically the CardSections.

   //add a single CardSection
   mAdapter.addCardSection(new GplayCardSection(6,"New Section","More"));

   //add CardSections
   List<GplayCardSection> newSections =  new ArrayList<GplayCardSection>();   
   newSections.add(new GplayCardSection(6,"Section 3","Other xx"));
   newSections.add(new GplayCardSection(10,"Section 4","Other xx1"));
   GplayCardSection[] dummy = new GplayCardSection[newSections.size()];   
   mAdapter.addCardSections(newSections.toArray(dummy)); 
   
   //Change all card sections
   mAdapter.setCardSections(newSections.toArray(dummy));
   

If you would like to add Cards and CardSections dinamically you can use something like this:

        ArrayList<Card> newCards = new ArrayList<Card>();        
        //Add cards to array.....
        //newCards.add(card);        
        mCardArrayAdapter.addAll(newCards);
        
        //Add a CardSection or check the code above to add more CardSections
        mAdapter.addCardSection(new GplayCardSection(6,"New Section","More"));
        
        //Don't forget to notify change to your cards adapter
        mCardArrayAdapter.notifyDataSetChanged();

Style

You can customize some properties with your style and drawable files. The quickest way to start with this would be to copy the specific style or drawable in your project and change them.

For the CardViewNative:

These are the main style properties:

  • native_list_card: common style applied to the single card item
  • native_list_card.base: style applied for base layout
  • native_list_card.thumbnail: style applied for thumbnail layout

margins:

    <dimen name="native_list_card_margin_left">0dp</dimen>
    <dimen name="native_list_card_margin_right">0dp</dimen>
    <dimen name="native_list_card_margin_bottom">4dp</dimen>
    <dimen name="native_list_card_margin_top">4dp</dimen>

For the CardView:

These are the main style properties:

  • list_card: common style applied to the single card item
  • list_card.base: style applied for base layout
  • list_card.thumbnail: style applied for thumbnail layout

margins:

    <dimen name="list_card_padding_left">8dp</dimen>
    <dimen name="list_card_padding_right">8dp</dimen>
    <dimen name="list_card_padding_bottom">6dp</dimen>
    <dimen name="list_card_padding_top">2dp</dimen>