Is it possible for the last element in the RecyclerView to be attached to the bottom if there is no need to scroll?

I create a RecyclerView basket, which displays all the items in the basket in the RecyclerView, and also has an extra from the bottom to the bottom, in which the basket is summed (total amount, coupon discount, if applicable, etc.).

If there are 3 elements in the basket, this looks normal, as the user will need to scroll down the page to view the “summary view”. However, if there are 1 or 2 elements, the first elements appear than the consolidated view, and then spaces. I would prefer the first paragraphs, then the space, then the summary view.

I tried to add blank elements, however, depending on the resolution of the device, it looks incompatible.

Current appearance if less than 3 elements (i.e. if scrolling is not required):

----------- | Item1 | | ------- | | Item2 | | ------- | | Summary | | ------- | | | | | | | | | ---------- 

Desired appearance:

 ----------- | Item1 | | ------- | | Item2 | | ------- | | | | | | | | | | ------- | | Summary | ---------- 
+5
source share
4 answers

I thought about your task and ended up writing code that you might find useful. There is a problem at this stage.

What I did adds an element decorator to the recycler view:

 recyclerView.addItemDecoration(new StickySummaryDecoration()); 

And here is my implementation of the main decorator (frankly, this is my first experience with item decorators, so it may not be optimal or completely correct, but I did my best):

 public class StickySummaryDecoration extends RecyclerView.ItemDecoration { @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getAdapter().getItemCount(); int lastVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findLastVisibleItemPosition(); int firstVisiblePosition = ((LinearLayoutManager) parent.getLayoutManager()) .findFirstCompletelyVisibleItemPosition(); if ((firstVisiblePosition == 0) && (lastVisibleItemPosition == (childCount - 1))) { View summaryView = parent.getChildAt(parent.getChildCount() - 1); int topOffset = parent.getHeight() - summaryView.getHeight(); int leftOffset = ((RecyclerView.LayoutParams) summaryView.getLayoutParams()).leftMargin; c.save(); c.translate(leftOffset, topOffset); summaryView.draw(c); c.restore(); summaryView.setVisibility(View.GONE); } } } 

So what I get with this.

The following summary in a short list:

Short list

You need to scroll down to view the summary in a long list:

Long list (you need to scroll down to see the summary element)

Now about the problem. This side effect when scrolling back in a long list is something that I haven't decided yet.

problem

My experiments are uploaded to github repo just in case :)

EDIT

Just now I came to me that the missing line item in the recycler view is my summary view handler, with GONE visibility. There must be a way to get it back ...

EDIT 2

I revised my first solution and slightly changed the code to account for a long list (in this case, the item decoration is disabled (I think it is closer to what you wanted to achieve)), this automatically fixes the missing line problem.

+2
source

Based on the answer from yigit, I created a working implementation. Extend ItemDecoration and override getItemOffsets() :

 @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getAdapter().getItemCount(); if (parent.getChildLayoutPosition(view) != childCount - 1) return; int lastViewBottom = calculateViewBottom(parent.getLayoutManager().findViewByPosition(childCount - 2)); view.measure(parent.getWidth(), parent.getHeight()); int height = view.getMeasuredHeight(); int topOffset = parent.getHeight() - lastViewBottom - height; if (topOffset < 0) topOffset = itemOffset; outRect.set(itemOffset, topOffset, itemOffset, itemOffset); } private int calculateViewBottom(View view) { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); return (int) view.getY() + view.getHeight() + params.topMargin + params.bottomMargin; } 
+3
source

This is not an intended use case, but you create an ItemDecorator and when getItemOffsets is called in the bottom view, evaluate it above it and set it as decorOffsetTop.

Each time you add an element, you must cancel the element decoding offsets so that the RecyclerView calls your callback again for a new calculation.

It will not be trivial, but it should work.

+2
source

Based on Lamorak's answer, I rewrote the code in Kotlin and modified it to fit RecyclerView padding and element fields:

 class LastSticksToBottomItemDecoration: RecyclerView.ItemDecoration() { override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { parent.adapter?.itemCount?.let { childCount -> if (parent.getChildLayoutPosition(view) != childCount - 1) return val lastViewBottom = when (childCount) { 1 -> 0 else -> parent.layoutManager?.findViewByPosition(childCount - 2)?.let { calculateViewBottom(it, parent) } ?: 0 } view.measure(parent.width, parent.height) max( 0, parent.height - parent.paddingTop - parent.paddingBottom - lastViewBottom - view.measuredHeight - view.marginTop - view.marginBottom ).let { topOffset -> outRect.set(0, topOffset, 0, 0) } } } private fun calculateViewBottom(view: View, parent: RecyclerView): Int = (view.layoutParams as ViewGroup.MarginLayoutParams).let { view.measure(parent.width, parent.height) view.y.toInt() + view.measuredHeight + it.topMargin + it.bottomMargin } } 
0
source

All Articles