android - How to add dividers and spaces between items in RecyclerView

android - How to add dividers and spaces between items in RecyclerView

Yitro Usman Author: Yitro Usman Date: 2022-08-12
android - How to add dividers and spaces between items in RecyclerView

All you need to know about android - How to add dividers and spaces between items in RecyclerView , in addintion to android - How to add dividers between items in a LazyColumn Jetpack Compose? , How to add a dynamic view in between items of RecyclerView in android? , How to add space between items in android listview? , android - how to add space between grid items in jetpack compose

  1. android - How to add dividers and spaces between items in RecyclerView
  2. Question:

    This is an example of how it could have been done previously in the ListView class, using the divider and dividerHeight parameters:

    <ListView
        android:id="@+id/activity_home_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@android:color/transparent"
        android:dividerHeight="8dp"/>
    

    However, I don't see such possibility in the RecyclerView class.

    <android.support.v7.widget.RecyclerView
        android:id="@+id/activity_home_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>
    

    In that case, is it ok to define margins and/or add a custom divider view directly into a list item's layout or is there a better way to achieve my goal?


    Solution 1:

    October 2016 Update

    The version 25.0.0 of Android Support Library introduced the DividerItemDecoration class:

    DividerItemDecoration is a RecyclerView.ItemDecoration that can be used as a divider between items of a LinearLayoutManager. It supports both HORIZONTAL and VERTICAL orientations.

    Usage:

    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
        layoutManager.getOrientation());
    recyclerView.addItemDecoration(dividerItemDecoration);
    

    Previous answer

    Some answers either use methods that have since become deprecated, or don't give a complete solution, so I tried to do a short, up-to-date wrap-up.


    Unlike ListView, the RecyclerView class doesn't have any divider-related parameters. Instead, you need to extend ItemDecoration, a RecyclerView's inner class:

    An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.

    All ItemDecorations are drawn in the order they were added, before the item views (in onDraw()) and after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State).

    Vertical spacing ItemDecoration

    Extend ItemDecoration, add a custom constructor which takes space height as a parameter and override the getItemOffsets() method:

    public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
    
        private final int verticalSpaceHeight;
    
        public VerticalSpaceItemDecoration(int verticalSpaceHeight) {
            this.verticalSpaceHeight = verticalSpaceHeight;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                RecyclerView.State state) {
            outRect.bottom = verticalSpaceHeight;
        }
    }
    

    If you don't want to insert space below the last item, add the following condition:

    if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) {
                outRect.bottom = verticalSpaceHeight;
    }
    

    Note: you can also modify outRect.top, outRect.left and outRect.right properties for the desired effect.

    Divider ItemDecoration

    Extend ItemDecoration and override the onDraw() method:

    public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    
        private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    
        private Drawable divider;
    
        /**
         * Default divider will be used
         */
        public DividerItemDecoration(Context context) {
            final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
            divider = styledAttributes.getDrawable(0);
            styledAttributes.recycle();
        }
    
        /**
         * Custom divider will be used
         */
        public DividerItemDecoration(Context context, int resId) {
            divider = ContextCompat.getDrawable(context, resId);
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
    
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
    
                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
    
                int top = child.getBottom() + params.bottomMargin;
                int bottom = top + divider.getIntrinsicHeight();
    
                divider.setBounds(left, top, right, bottom);
                divider.draw(c);
            }
        }
    }
    

    You can either call the first constructor that uses the default Android divider attributes, or the second one that uses your own drawable, for example drawable/divider.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle">
        <size android:height="1dp" />
        <solid android:color="#ff992900" />
    </shape>
    

    Note: if you want the divider to be drawn over your items, override the onDrawOver() method instead.

    Usage

    To use your new class, add VerticalSpaceItemDecoration or DividerSpaceItemDecoration to RecyclerView, for example in your fragment's onCreateView() method:

    private static final int VERTICAL_ITEM_SPACE = 48;
    private RecyclerView recyclerView;
    private LinearLayoutManager linearLayoutManager;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_feed, container, false);
    
        recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_home_recycler_view);
        linearLayoutManager = new LinearLayoutManager(getActivity());
        recyclerView.setLayoutManager(linearLayoutManager);
    
        //add ItemDecoration
        recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));
        //or
        recyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
        //or
        recyclerView.addItemDecoration(
                new DividerItemDecoration(getActivity(), R.drawable.divider));
    
        recyclerView.setAdapter(...);
    
        return rootView;
    }
    

    There's also Lucas Rocha's library which is supposed to simplify the item decoration process. I haven't tried it though.

    Among its features are:

    • A collection of stock item decorations including:
    • Item spacing Horizontal/vertical dividers.
    • List item

    Solution 2:

    Just add

    recyclerView.addItemDecoration(new DividerItemDecoration(getContext(),
                    DividerItemDecoration.VERTICAL));
    

    Also you may need to add the dependency
    implementation 'com.android.support:recyclerview-v7:28.0.0'

    For customizing it a little bit you can add a custom drawable:

    DividerItemDecoration itemDecorator = new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL);
    itemDecorator.setDrawable(ContextCompat.getDrawable(getContext(), R.drawable.divider));
    

    You are free to use any custom drawable, for instance:

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle">
        <solid android:color="@color/colorPrimary"/>
        <size android:height="0.5dp"/>
    </shape>
    

    Solution 3:

    Might I direct your attention to this particular file on GitHub by Alex Fu: https://gist.github.com/alexfu/0f464fc3742f134ccd1e

    It's the DividerItemDecoration.java example file "pulled straight from the support demos".(https://plus.google.com/103498612790395592106/posts/VVEB3m7NkSS)

    I was able to get divider lines nicely after importing this file in my project and add it as an item decoration to the recycler view.

    Here's how my onCreateView look like in my fragment containing the Recyclerview:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_recycler_view, container, false);
    
        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
    
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    
        return rootView;
    }
    

    I'm sure additional styling can be done, but it's a starting point. :)

  3. android - How to add dividers between items in a LazyColumn Jetpack Compose?
  4. Question:

    I have a LazyColumn that looks like this:

    LazyColumn (
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(bookList) { book ->
            InProgressBookItem(book = book)
        }
    }
    

    How can I add a line between each item in the list, similar to using an item decoration on the old RecyclerView?


    Solution 1:

    Currently, with 1.0.x there is no built–in way to add dividers. However, you can just add a Divider in the LazyListScope.

    Something like:

    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        items(itemsList){
            Text("Item at  $it")
            Divider(color = Color.Black)
        }
    }
    

    If you do not want the last item to be followed by a Divider, you can add dividers to items according to their indices:

    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        itemsIndexed(itemsList) { index, item ->
    
            Text("Item at index $index is $item")
    
            if (index < itemsList.lastIndex)
                Divider(color = Color.Black, thickness = 1.dp)
        }
    }
    

    enter image description here

    Solution 2:

    Simple:

    LazyColumn (
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(bookList) { book ->
            InProgressBookItem(book = book)
            Divider(color = Color.Black, thickness = 1.dp)
        }
    }
    

  5. How to add a dynamic view in between items of RecyclerView in android?
  6. Question:

    I need to add a small strip in between items of a RecyclerView. This strip can come after different number of items in a list. This needs to be done dynamically. I need to implement something like what FitBit has done:enter image description here

    I also need the first row i.e. the one saying "This Week" to stick on top even if the page scrolls down.


    Solution 1:

    You should use the concept of different view types using getItemViewType(int). Then on onCreateViewHolder(ViewGroup, int) you can check which type you should inflate/create.

    Example:

    @Override
    public int getItemViewType(int position) {
        // you should return the view type, based on your own dynamic logic
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             // handle each view type accordingly
         }
    }
    

    Solution 2:

    Simple:

    LazyColumn (
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(bookList) { book ->
            InProgressBookItem(book = book)
            Divider(color = Color.Black, thickness = 1.dp)
        }
    }
    

    Solution 3:

    You can use the concept of multiple view types in your RecyclerView, Just by using getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().

    For example you can use below model:

        public class Data{
               int field1;
               float filed2;
               int rowType // 1,2,2,...N this will fill by you whenever you will    
                           //creating arraylist for your recyclerview
         }
    
    
        public class Custome Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        ArrayList<Data> mItems;
    
        class ViewHolderRowType1 extends RecyclerView.ViewHolder {
            ...
        }
    
        class ViewHolderRowType2 extends RecyclerView.ViewHolder {
            ...
        }
    
        ....
        class ViewHolderRowTypeN extends RecyclerView.ViewHolder {
            ...
        }
    
        @Override
        public int getItemViewType(int position) {
            return mItems.get(position).rowType; 
            //or
            //return positon%2; // This will based on your condition        
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             switch (viewType) {
                 case 0: return new ViewHolderRowType0(...);
                 case 1: return new ViewHolderRowType1(...);
                 ...
                 case N: return new ViewHolderRowTypeN(...);
             }
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
    
        //Just check which view type is going to bind and then fill the data accordingly in your rows
    
    
          if(vh instanceof ViewHolderRowType1){
           // Fill the data for first view type
    
    
          } else if (vh instanceof ViewHolderRowType2) {
           // Fill the data for second view type
    
    
          } else if (vh instanceof ViewHolderRowTypeN){
           // Fill the data for Nth view type
    
    
          }
    
        }
    

    For your sticky "this view weak", you can add it at top of your RecyclerView and then handle it by scroll Event of RecyclerView

  7. How to add space between items in android listview?
  8. Question:

    I would like to add padding between EACH item in a listview, but I would like to keep the default divider as I think it is aesthetically pleasing. Anything wider looks ugly.

    I currently have:

    <com.example.practice.MyListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:drawSelectorOnTop="false"
        android:layout_below="@id/name" />
    

    Now, I have tried using a transparent divider, and this succeeds at getting the spacing I want, but then I don't see the little line. And if I don't use a transparent divider than I have a huge thick ugly line. I want to keep the default line shown, and just add some spacing on the top part of each listview item.


    Solution 1:

    You wouldn't be able to achieve what you want as simple as that then.

    Step one: Set the divider as transparent, and make the height a tad larger:

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:divider="@android:color/transparent"
        android:dividerHeight="8dp"/>
    

    Step Two: In order to achieve the 'little line' effect, you can add a custom drawable as the list view item background, say the list view item is defined as 'list_item.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="match_parent"
    <-- Add a Custom background to the parent container of the listview items -->
    android:background="@drawable/list_item_selector"
    android:orientation="vertical" >
    <-- Rest of the item layout -->
    </LinearLayout>
    

    Of course, that item can be anything you like it to be, mine is:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    
    <item>
      <shape 
        android:shape="rectangle">
            <stroke android:width="1dp" android:color="@color/bg_gray" />
            <solid android:color="@android:color/white" />
        </shape>
    </item>
    <item android:bottom="1dp"> 
        <shape 
            android:shape="rectangle">
            <stroke android:width="1dp" android:color="#FFDDDDDD" />
            <solid android:color="#00000000" />
        </shape>
    </item>
    </layer-list>
    

    But that would then disable the 'Holo Selector' effect, where whenever you click, or highlight an item on the listview, there is a Holo Blue color drawn over it, that's why if you notice on the list item background we didn't use a layer list drawable, we used a selector named 'list_item_selector'.

    Here's the selector, which uses the layer list drawable when not pressed, and uses a Holo-blue color when pressed:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
      <item 
        android:state_pressed="false"
        android:drawable="@drawable/list_item_bg2"        
        />
     <item android:state_pressed="true"
        android:drawable="@color/holo_blue"
        />
    </selector>
    

    EDIT for Comment

    Absolutely possible, you can define a set height for list view items, however, it is recommended to set a minimum height, rather than a predefined height that never changes.

    Say this is my list item layout:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <ImageView
        android:id="@+id/grid_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />
    
    <TextView
        android:id="@+id/grid_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:gravity="center_vertical"
        android:layout_toRightOf="@+id/grid_image"
        android:minHeight="48dp"
        android:padding="8dp"
        android:textIsSelectable="false" />
    
    </RelativeLayout>
    

    All needed would be,

    Step One: Define the min height, or max height, as you prefer, in the dimens.xml file contained in the values/ folder. Why? Because the height should definitely change based on the layout, and you can define different dimens.xml for each device density.

    in the dimens.xml, say:

    <resources>
    
    <!-- List Item Max and Min Height -->
    
        <dimen name="list_item_min_height">48dp</dimen>
        <dimen name="list_item_max_height">96dp</dimen>
        <dimen name="list_item_set_height">64dp</dimen>
    
    </resources>
    

    And then use whichever value for the parent LinearLayout of you list item's layout.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="@dimen/list_item_min_height" >
    

    And that's it for that topic, now to center the text, it's even simpler:

    • If you are using a TextView and is wrapped into a RelativeLayout, use: android:layout_centerVertical="true"

    • If you are using a LinearLayout, use: android:layout_gravity="center_vertical"

    and couple that with: NOTE This only works if you didn't set the height to wrap_content, otherwise it is irrelevant.

      android:gravity="center_vertical"
    

    Hope that helps.

    Solution 2:

    I don't know if I understand your question precisely. If you want the divider to be transparent so you see a peace of the background between each ListView so it gives a kind of 3D effect when scrolling. You could do it this way:

    In your list_item xml give the LinearLayout the background color you want for example:

    <LinearLayout 
    android:id="@+id/listItem"
    android:layout_width="match_parent"
    android:layout_height="match_parent"    
    android:padding="4dip"
    android:orientation="horizontal"
    android:background="#FFFFFF"
    >
    

    Then give your ListView a background color like this:

    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragmentListView"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    android:divider="@android:color/transparent"
    android:dividerHeight="8dip"
    android:background="#0000FF"
    android:cacheColorHint="@android:color/transparent"
    android:drawSelectorOnTop="true"
    />
    

    Now your ListView scrolls over your background

    I hope this is what you wanted.

    Solution 3:

    Also one more way to increase the spacing between the list items is that you add an empty view to your adapter code by providing the layout_height attribute with the spacing you require. For e.g. in order to increase the bottom spacing between your list items add this dummy view(empty view) to the end of your list items.

    <View
    android:layout_width="match_parent"
    android:layout_height="15dp"/>
    

    So this will provide a bottom spacing of 15 dp between list view items. You can directly add this if the parent layout is LinearLayout and orientation is vertical or take appropriate steps for other layout. Hope this helps :-)

  9. android - how to add space between grid items in jetpack compose
  10. Question:

    I am currently trying to implement a gridview, it consists of 2 columns. I know i can implement my own grid view using columns and rows, but i just want to use existing approach although it is experimental.

    @Composable
    fun MyGridScreen() {
        LazyVerticalGrid(cells = GridCells.Fixed(2), modifier = Modifier.fillMaxSize(),contentPadding = PaddingValues(12.dp)) {
            items(15) {
                Box(
                    contentAlignment = Alignment.Center,
                    modifier = Modifier
                        .padding(10.dp)
                        .height(80.dp)
                        .background(Color.Red)
                        .aspectRatio(1f)
    
                ) {
                    Text("Grid item $it", color = Color.White)
                }
            }
        }
    } 
    

    Below is the result i achieved. I can't put space below the item :(

    enter image description here


    Solution 1:

    You need to set verticalArrangement and horizontalArrangement properties on the LazyVerticalGrid composable.

    This will space the items by 10.dp in both the vertical and horizontal axis.

     @Composable
        fun MyGridScreen() {
            LazyVerticalGrid(
               cells = GridCells.Fixed(2),
               modifier = Modifier.fillMaxSize(),
               contentPadding = PaddingValues(12.dp), 
               verticalArrangement = Arrangement.spacedBy(10.dp), 
               horizontalArrangement = Arrangement.spacedBy(10.dp
             ) {
                  items(15) {
                     Box(
                        contentAlignment = Alignment.Center,
                        modifier = Modifier
                            .padding(10.dp)
                            .height(80.dp)
                            .background(Color.Red)
                            .aspectRatio(1f)
    
                    ) {
                        Text("Grid item $it", color = Color.White)
                }
            }
        }
    } 
    

    Solution 2:

    Padding is exactly what you need in this case. But you need to understand that in compose modifiers order means a lot. By moving background modifier in between padding and sizes modifiers, you'll get what you need. And clickable ripple will work the same.

    Check out more how modifiers order works: https://stackoverflow.com/a/65698101/3585796

    .padding(10.dp)
    .background(Color.Red)
    .height(80.dp)
    .aspectRatio(1f)
    

    enter image description here

    p.s. if you need your items to be a square, just put your box in one more box. Looks like width of items in LazyVerticalGrid gets overridden by the grid to fill max width, not sure if that's a bug or a feature.