Skip to content Skip to sidebar Skip to footer

How To Set Recyclerview Item Max Height Relative To Parent

I've almost got this working by following the suggestions in this question Android percent screen width in RecyclerView item However that sets a height for all views, same height r

Solution 1:

I'd recommend the same approximate strategy I used in my linked answer: set the max height when you create the ViewHolder. However, generic View does not support the maxHeight property, so we'll leverage ConstraintLayout to achieve what we want.

If the root view for your item views is already a ConstraintLayout, great, otherwise you can wrap whatever you do have inside it. In this contrived example, I'm using this layout (the FrameLayout is using 0dp width and wrap_content height in the XML):

<androidx.constraintlayout.widget.ConstraintLayout...><FrameLayout...><TextView.../></FrameLayout></androidx.constraintlayout.widget.ConstraintLayout>

At creation time, I set the max height of the FrameLayout to be 60% of the parent's height:

overridefunonCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val itemView = inflater.inflate(R.layout.recycler_item, parent, false)
    val holder = MyViewHolder(itemView)

    val params = holder.frame.layoutParams as ConstraintLayout.LayoutParams
    params.matchConstraintMaxHeight = (parent.height * 0.6).toInt()
    holder.frame.layoutParams = params

    return holder
}

Solution 2:

Overwriting measureChildWithMargins is the way to go, however to get it actually working you need custom LayoutParams so you can "carry over" data from your adapter or straight up inflate your desired percentages out of XML.

This layout manager will handle it:

openclassPercentLinearLayoutManager : LinearLayoutManager {constructor(context: Context?) : super(context)
    constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

    // where we actually force view to be measured based on custom layout paramsoverridefunmeasureChildWithMargins(child: View, widthUsed: Int, heightUsed: Int) {
        val pp = child.layoutParams as PercentParams
        if (pp.maxPercentHeight <= 0.0f)
            super.measureChildWithMargins(child, widthUsed, heightUsed)
        else {
            val widthSpec = getChildMeasureSpec(width, widthMode,
                    paddingLeft + paddingRight + pp.leftMargin + pp.rightMargin + widthUsed, pp.width,
                    canScrollHorizontally())
            val maxHeight = (height * pp.maxPercentHeight).toInt()
            val heightSpec = when (pp.height) {
                ViewGroup.LayoutParams.MATCH_PARENT -> View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.EXACTLY)
                ViewGroup.LayoutParams.WRAP_CONTENT -> View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
                else -> View.MeasureSpec.makeMeasureSpec(min(pp.height, maxHeight), View.MeasureSpec.AT_MOST)
            }
            child.measure(widthSpec, heightSpec)
        }
    }

    // everything below is needed to generate custom paramsoverridefuncheckLayoutParams(lp: RecyclerView.LayoutParams) = lp is PercentParams

    overridefungenerateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return PercentParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    overridefungenerateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams {
        return PercentParams(lp)
    }

    overridefungenerateLayoutParams(c: Context, attrs: AttributeSet): RecyclerView.LayoutParams {
        return PercentParams(c, attrs)
    }

    classPercentParams : RecyclerView.LayoutParams {/** Max percent height of recyclerview this item can have. If height is `match_parent` this size is enforced. */var maxPercentHeight = 0.0fconstructor(c: Context, attrs: AttributeSet) : super(c, attrs){
            val t = c.obtainStyledAttributes(attrs, R.styleable.PercentLinearLayoutManager_Layout)
            maxPercentHeight = t.getFloat(R.styleable.PercentLinearLayoutManager_Layout_maxPercentHeight, 0f)
            t.recycle()
        }
        constructor(width: Int, height: Int) : super(width, height)
        constructor(source: MarginLayoutParams?) : super(source)
        constructor(source: ViewGroup.LayoutParams?) : super(source)
        constructor(source: RecyclerView.LayoutParams?) : super(source)
    }
}

To handle inflation from XML custom attribute is needed, add it to values/attrs.xml:

<declare-styleablename="PercentLinearLayoutManager_Layout"><attrname="maxPercentHeight"format="float"/></declare-styleable>

Then you have two options:

1 - alter custom params inside onBindViewHolder to have per-item control:

val lp = holder.itemView.layoutParams as PercentLinearLayoutManager.PercentParams
if(modifyThisViewHolderHeight) {
    lp.maxPercentHeight = 0.6f
} else {
    lp.maxPercentHeight = 0.0f// clear percentage in case viewholder is reused
}

2 - use custom attribute inside your viewholder layout:

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="wrap_content"android:layout_height="wrap_content"app:maxPercentHeight ="0.6"><!-- rest of layout--></FrameLayout>

Post a Comment for "How To Set Recyclerview Item Max Height Relative To Parent"