Sometimes, we will encounter the need for a linear list layout where wrap_content displays all items directly when the number of items is small, but when the number of items exceeds a certain number, the height is fixed so that it can slide to show more items. So our first thought was to use RecyclerView, and I don’t think anyone would use ListView or write a custom ViewGroup.

However, when we use RecyclerView+maxHeight, we will find that maxHeight does not work.

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxHeight="100dp" />
Copy the code

So why does this happen? As I mentioned in a previous blog post: RecyclerView transfers onMeasure to LayoutManager, and There is an isAutoMeasureEnabled () method in LayoutManager to configure whether LayoutManager enables self-measurement. The LinearLayoutManager or GridLayoutManager we commonly use returns true by default.

Blog address

Implementation approach

So the implementation idea is very simple: set a maxLine parameter, when RecyclerView itemCount is less than this value isAutoMeasureEnabled() return true, let LayoutManager to measure. When itemCount is greater than maxLine, override the onMeasure method to set the RecyclerView limit.

Code implementation

class MaxLineLinearLayoutManager : LinearLayoutManager {

    private var mMaxLine = 0

    constructor( context: Context? , maxLine:Int
    ) : super(context) {
        Helper.checkMaxCount(maxLine)
        this.mMaxLine = maxLine
    }

    constructor( context: Context? , orientation:Int,
        reverseLayout: Boolean,
        maxLine: Int
    ) : super(context, orientation, reverseLayout) {
        Helper.checkMaxCount(maxLine)
        this.mMaxLine = maxLine
    }

    override fun onMeasure(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State,
        widthSpec: Int,
        heightSpec: Int
    ) {
        if (itemCount <= mMaxLine || itemCount == 0) {
            super.onMeasure(recycler, state, widthSpec, heightSpec)
            return
        }

        val child = recycler.getViewForPosition(0)

        //
        addView(child)
        measureChildWithMargins(child, 0.0)
        val itemWidth = getDecoratedMeasuredWidth(child)
        val itemHeight = getDecoratedMeasuredHeight(child)
        removeAndRecycleView(child, recycler)

        val widthMode = View.MeasureSpec.getMode(widthSpec)
        val heightMode = View.MeasureSpec.getMode(heightSpec)
        var width = 0
        var height = 0

        if (orientation == HORIZONTAL) {
            height = if (heightMode == View.MeasureSpec.EXACTLY) {
                View.MeasureSpec.getSize(heightSpec)
            } else {
                itemHeight
            }
            width = itemWidth * mMaxLine
        } else {
            width = if (widthMode == View.MeasureSpec.EXACTLY) {
                View.MeasureSpec.getSize(widthSpec)
            } else {
                itemWidth
            }
            height = itemHeight * mMaxLine
        }

        setMeasuredDimension(width, height)
    }

    override fun isAutoMeasureEnabled(a): Boolean {
        if (itemCount <= mMaxLine) {
            return super.isAutoMeasureEnabled()
        }
        return false}}Copy the code

The code is simple enough that you should be able to understand it without comments. If you don’t understand, check out my previous article analyzing custom LayoutManager.

Blog address

 recyclerView.layoutManager = MaxLineLinearLayoutManager(this, maxLine = 3)
Copy the code

The source address

Github.com/simplepeng/…