How can I use layout_weight in RecyclerView or ListView item view to have items of equal height fill up the available space on screen?
Asked Answered
I

6

19

I currently have a need to use a RecyclerView (or ListView) but the number of items is fixed at 4. I want those 4 items to equally use the available space on the screen. The RecyclerView is the only view on the screen except for the app bar. I.e. RecyclerView has layout_height set to match_parent. I chose the RecyclerView because the items have different layouts depending on model state.

I haven't looked it up yet, but I'm sure I can set the height programmatically in Java code for each item. But that seems inelegant if I can specify it in the XML. Similar questions to my right, as I write this, answer that question.

I've tried the following in the item_layout.xml file, the way layout_weight is used in other scenarios:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_weight="1"
    android:layout_height="0dp">...</LinearLayout>

But then nothing shows on screen.

What I'd like to know is, can I do this in the layout XML with layout_weight, or something else? If so, how?

Incorrigible answered 2/8, 2015 at 21:17 Comment(1)
if the number of views is fixed. I completely discourage you to use listview. just put for linear layout with weight equal to one.Intervenient
R
32

As the API reference says, layout_weight is an attribute of LinearLayout, so it cannot be used with other ViewGroups like, for instance, RecyclerView and ListView. Instead, you have to set the item's height programmatically.

Since you seem worried about your code's cleanliness, I think this is a rather elegant solution in case you use a RecyclerView:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater
            .from(parent.getContext())
            .inflate(R.layout.item_list, null);

    int height = parent.getMeasuredHeight() / 4;
    int width = parent.getMeasuredWidth();

    view.setLayoutParams(new RecyclerView.LayoutParams(width, height));

    return new ViewHolder(view);
}
Rattle answered 6/6, 2016 at 14:34 Comment(4)
parent.getMeasuredHeight() returns '0' value!Wardmote
Is getMeasuredHeight the problem here?Rattle
For those having parent.getMeasuredSomething. maybe, you have your recyclerview have a wrap_content so it will initially have 0 height and the succeeding views will be adjusted to that.Conductive
@Rattle I am getting '0' for both height and width. MyRecyclerview has width set as "match_parent" and height as "wrap_content" . For the adapter xml file, both width and height are wrap_content. Any help would be appreciated here thanks.Babur
P
12

Try this, work for me

int weight = 3; //number of parts in the recycler view. 

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View rowView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row,parent,false);
        rowView.getLayoutParams().height = parent.getHeight() / weight;
        return new ViewHolder(rowView);
    }
Pewit answered 12/7, 2016 at 4:43 Comment(2)
you have a syntax error, it should be parent.getContext(), the edit it too small for me to change.Abortion
This doesn't work for me. parent.getHeight() is -2.Poppy
V
1

I ran into this issue, as I had a RecyclerView with variable entries. My horizontal RecyclerView needed to fit at least three views without overflowing, so I constructed my views to be resized at runtime to fit the screen.

class MyRecyclerView extends RecyclerView.Adapter<ViewHolder> {

    int mViewWidth;

    public MyRecyclerView(Context context) {
      mViewWidth = defineViewWidth(context);
    }

    private int defineViewWidth(Context context) {
      int definedWidth = (int) context.getResources().getDimension(R.dimen.default_view_width);
      DisplayMetrics dm = new DisplayMetrics();
      WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
      wm.getDefaultDisplay().getMetrics(dm);
      if (definedWidth * 3 > dm.widthPixels) {
        return dm.widthPixels / 3;
      }
      return definedWidth;
    }

    ...

}

Once you have a width defined, you need to set the view width:

@Override
public ViewHolder onCreateViewHolder(ViewHolder holder, int position) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row, parent, false);
    ViewGroup.LayoutParams params = v.getLayoutParams();
    params.width = mViewWidth;
    v.setLayoutParams(params);
    return new ViewHolder(v);
}

You could easily modify this to make sure your RecyclerView fits as many elements (vertically or horizontally) as you need.

Volscian answered 26/5, 2016 at 22:5 Comment(0)
P
1

It could be simply accomplished using a custom ItemDecoration:

class SameLinearSpaceItemDecoration(
        private val minSpanCount: Int = 1
        , private val paddingHorizontal: Int = 0
        , private val paddingVertical: Int = 0
) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)

        if (parent.layoutManager !is LinearLayoutManager) return

        val spanCount = (Math.max(minSpanCount, parent.adapter?.itemCount ?: 1))
        if ((parent.layoutManager as LinearLayoutManager).orientation == RecyclerView.HORIZONTAL) {
            view.layoutParams.width = (parent.measuredWidth - paddingHorizontal - paddingHorizontal) / spanCount
        } else { // vertical
            view.layoutParams.height = (parent.measuredHeight - paddingVertical - paddingVertical) / spanCount
        }

        view.setPadding(if (paddingHorizontal == 0) view.paddingLeft else paddingHorizontal
                , if (paddingVertical == 0) view.paddingTop else paddingVertical
                , if (paddingHorizontal == 0) view.paddingRight else paddingHorizontal
                , if (paddingVertical == 0) view.paddingBottom else paddingVertical)
    }

}
Pelion answered 6/11, 2019 at 18:6 Comment(0)
J
0

By ItemDecoration:

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    int itemWidth = parent.getMeasuredWidth() / 7;
    view.getLayoutParams().width = itemWidth;
}
Jaella answered 4/9, 2018 at 6:4 Comment(0)
S
0

I have a similar issue just to keep items in horizontal recycleView spread equally which means each of them has an equal space, here is my CircleItemDecoration (in Kotlin):

class CircleItemDecoration() : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        Timber.d("parent.width = ${parent.width} outRect = ${view.width}")
        val itemCount = parent.adapter?.itemCount ?: 0
        if (itemCount <= 1) return
        if (parent.getChildAdapterPosition(view) != 0) {
            outRect.left = (parent.width - view.width * itemCount) / (itemCount - 1)
        }
    }
}

and one trick for this is we need to "addItemDecoration" in doOnPreDraw, which ensure the measure of each UI element has been calculated:

rvSpeedControl.doOnPreDraw {
    rvSpeedControl.addItemDecoration(CircleItemDecoration())
}
Siltstone answered 26/10, 2023 at 21:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.