41

How would I get a RecyclerView's item height to be, say, 30% of the screen's height?

I can't simply use a ConstraintLayout or a PercentFrameLayout/PercentRelativeLayout, since those position child layouts relative to parent layouts, whose size might not match the screens.

Preferably, I'd like to do this in pure XML (and not have to use any Runnables to get the screen height dynamically).

EDIT

To clarify, since a lot of solutions suggest restraining the height of the RecyclerView, that's not the intent. The RecyclerView should still populate the screen. Here's what I'm talking about, using the screen width (instead of height):

enter image description here

The idea is to get the card's width to be 33% the screen's width, while keeping the RecyclerView's width unaltered as it scrolls past the screen horizontally.

davidchuyaya
  • 3,192
  • 2
  • 13
  • 23
  • Let me know if this explanation is unclear: I know you can set percentages according to the size of the enclosing PercentLayout or ConstraintLayout. However, since I'm dealing with items in a RecyclerView, the enclosing layout is neither. – davidchuyaya Jul 06 '18 at 00:47
  • I think You should read this [don't-reload-application-when-orientation-changes](https://stackoverflow.com/questions/5913130/dont-reload-application-when-orientation-changes) – Mithun Halmare Jul 16 '18 at 08:40
  • What does that question have to do with this one? – davidchuyaya Jul 17 '18 at 14:36

9 Answers9

83

You can override one of the LayoutManager methods used by your RecyclerView to force specific size:

recyclerView.setLayoutManager(new LinearLayoutManager(this){
    @Override
    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
        // force height of viewHolder here, this will override layout_height from xml
        lp.height = getHeight() / 3;
        return true;
    }
});

This is assuming your RecyclerView fits the screen.

If you want more complex solution (custom layout manager with per-item control & percent inflation straight from from XML) see this answer.

Pawel
  • 10,706
  • 1
  • 20
  • 27
41

There is no way to do this in XML that I'm aware of. However, you can do it in Java (without any Runnables etc) by setting your ViewHolder's itemView's LayoutParams before returning the ViewHolder in your adapter's onCreateViewHolder() method:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View itemView = inflater.inflate(R.layout.itemview, parent, false);

    ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
    layoutParams.height = (int) (parent.getHeight() * 0.3);
    itemView.setLayoutParams(layoutParams);

    return new MyViewHolder(itemView);
}
Ben P.
  • 44,716
  • 5
  • 70
  • 96
  • how do i apply this for my width? I want to show 80% of a card on the screen and right next to it, i want to show the next card content peeking through. is there a way to do this with recycler view , as a carousel? –  Aug 15 '19 at 19:42
  • did you think that `parent.getHeight()` or `parent.getWidth()` can return `0`, especially if you put your `RecyclerView` inside `ConstraintLayout`: `` – user924 Jan 08 '20 at 17:03
  • Declaring `layout_width="0dp"`, and then setting constraints to define the width, will result in a `getWidth()` of a real value, and not 0. – Ben P. Jan 08 '20 at 19:08
  • @BenP. just try it yourself, it will be a real value of course but not than soon, when `onCreateViewHolder` is called it will be 0, but then a little bit later it will be a real value – user924 Jan 09 '20 at 10:47
  • This is not a valid answer. This is not applicable for horizontal scrolling behaviour. It decreases the view width – Shaon Apr 28 '20 at 09:49
  • @user924 Did you find a solution for this? – Cilenco Sep 17 '20 at 11:23
7

You can use Display metrices

Define display metrices in constructor, or use any other static method initialization.

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

Now you can use it in onCreateViewHolder()

View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);

//setting the height programmatically
itemView.getLayoutParams().height = metrics.heightPixels/3;
itemView.requestLayout();

Or You can use in onBindViewHolder() on parent layout of layout.

Ranjan
  • 1,214
  • 14
  • 34
4

You can not set percent height of item by XML only. Reason being it is an item which will be inflated at run-time, it is not a activity like layout. Which you write in your XML.

So now i tell you two ways to achieve percent / weight

1st Preferred way is to override height in onCreateViewHolder() method inside your Adapter

private int height = 0; // declared height globally in Adapter class to reuse it.

@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    if (height == 0)
        height = (int) (parent.getMeasuredHeight() * .3);
    itemView.setMinimumHeight(height);
    return new ItemViewHolder(itemView);
}

2nd Hack is to use SDP library.

Use height of item in XML in sdp say _160sdp. It will not give you exact results of 30%. But it can adjust according to screen.

Khemraj Sharma
  • 46,529
  • 18
  • 168
  • 182
2

I achieved this by doing the following inside of my custom RecyclerView Item layout:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val newMeasuredWidth =
        (MeasureSpec.getSize(widthMeasureSpec) * WIDTH_ADJUSTMENT_RATIO).toInt()
    super.onMeasure(
        MeasureSpec.makeMeasureSpec(newMeasuredWidth, MeasureSpec.EXACTLY),
        heightMeasureSpec
    )
}
hermt2
  • 754
  • 1
  • 11
  • 29
2
    DisplayMetrics displayMetrics = new DisplayMetrics();
    ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    int devicewidth = (int) (displayMetrics.widthPixels * 0.8);
    view.getLayoutParams().width = devicewidth;
    return new YourAdapter.ViewHolder(view);
0

Try this way. Just one time

inner class MyFeatureProductViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
    val tvName = v.findViewById<TextView>(R.id.tvName)
    val tvPrice = v.findViewById<TextView>(R.id.tvPrice)
    val tvItemUploadTime = v.findViewById<TextView>(R.id.tvItemUploadTime)
    val ivItem = v.findViewById<ImageView>(R.id.ivItem)

    init {
        v.layoutParams.width =  itemView.measuredWidth - 20
        v.requestLayout()
    }
}
Shaon
  • 1,404
  • 14
  • 17
0

If you don't mind the exact percentage, you can achieve the desired view by setting the android:paddingLeft and android:paddingRight and also setting the android:clipToPadding to false. This also makes first and last item align in center.

Amir
  • 826
  • 12
  • 20
-2

Well I just tried to fix the issue you're having. I played with few things. Steps as follows:

  1. MainActivity View Where RecyclerView has height of exactly 30% of screen. enter image description here
  2. Recycler View Items Height and Width must be match_parent. enter image description here

What's happening there?

  1. recyclerHolder has exactly 30% height of screen height there is no doubt.

  2. Inside recyclerHolder we've RecyclerView which takes all those 30% height of parent and takes full width.

  3. ListItem Layout takes full height and width of parent, where parent is RecyclerView which has exactly 30% height of screen.

I tried this and works perfectly. There you can find 2 empty views just to test if those fits perfectly on top and bottom, the result yes it does. put whatever inside list item it's gonna take 30% of screen height.

Yubaraj Shrestha
  • 788
  • 1
  • 8
  • 14