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>
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;
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With