Android percent screen width in RecyclerView item

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.


Solution 1:

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.

Solution 2:

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);
}

Solution 3:

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.

Solution 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.

Solution 5:

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
    )
}