Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get child view height in RecyclerView ItemDecoration.getItemOffsets

I am attempting to create an ItemDecoration that will maintain a minimum height for a RecyclerView by dynamically adding padding to the last item.

To calculate the amount of padding I need to know the total height of all children. I am approximating this by getting the height of a single view and multiplying it by the number of items in the Adapter.

The problem I am experiencing is that view.getHeight() doesn't always return the correct height. Usually the first time getItemOffsets() is called the height is much smaller than it should be (e.g. 30px vs 300px). This happens even if I give the child views a fixed height in the layout XML. I am guessing this has something to do with the measure/layout cycle by I am unsure of how to get the correct view dimensions in the ItemDecoration.

What is the correct way to get the child views height programmatically in getItemOffsets()?

ItemDecoration:

public class MinHeightPaddingItemDecoration extends RecyclerView.ItemDecoration {
    private static final String LOG_TAG = MinHeightPaddingItemDecoration.class.getSimpleName();

    private int mMinHeight;

    public MinHeightPaddingItemDecoration(int minHeight) {
        super();
        mMinHeight = minHeight;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
        int itemCount = state.getItemCount();
        int lastPosition = itemCount - 1;
        int itemPosition = recyclerView.getChildAdapterPosition(view);
        int layoutPosition = recyclerView.getChildLayoutPosition(view);

        // If this view isnt on screen then do nothing
        if (layoutPosition != RecyclerView.NO_POSITION && itemPosition == lastPosition) {

            // NOTE: view.getHeight() doesn't always return the correct height, even if the layout is given a fixed height in the XML
            int childHeight = view.getHeight();

            int totalChildHeight = childHeight * itemCount;

            int minHeight = getMinHeight();
            if (totalChildHeight < minHeight) {
                outRect.bottom = minHeight - totalChildHeight;
            }
        }
    }

    private int getMinHeight() {
        return mMinHeight;
    }
}

recycler_view_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="controller" type="my.ViewController"/>
    </data>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:clickable="true"
        android:onClick="@{() -> controller.doSomething()}">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Child layout"
                    />
        </RelativeLayout>

    </android.support.v7.widget.CardView>
</layout>
like image 223
Justin Fiedler Avatar asked Jan 03 '23 12:01

Justin Fiedler


2 Answers

I found this workaround for fixed sized child views. I am still not sure how to handle dynamically sized layouts.

// NOTE: view.getHeight() doesn't always return the correct height 
// NOTE: even if the layout is given a fixed height in the XML. 
// NOTE: Instead directly access the LayoutParams height value
int childHeight = view.getLayoutParams().height;
like image 200
Justin Fiedler Avatar answered Jan 31 '23 18:01

Justin Fiedler


I found a kind-of hack, after reading https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations.

Before calculating padding based on child.width, I manually measure the View, if needed:

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    if(view.width == 0) fixLayoutSize(view, parent)
    calculatePadding(outRect, view, parent, state)
}

private fun fixLayoutSize(view: View, parent: ViewGroup) {
    if (view.layoutParams == null) {
        view.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
        )
    }
    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
    val childWidth = ViewGroup.getChildMeasureSpec(
        widthSpec, parent.paddingLeft + parent.paddingRight, view.layoutParams.width
    )
    val childHeight = ViewGroup.getChildMeasureSpec(
        heightSpec, parent.paddingTop + parent.paddingBottom, view.layoutParams.height
    )
    view.measure(childWidth, childHeight)
    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}
like image 26
Luke Avatar answered Jan 31 '23 17:01

Luke