Is it possible to have the last item in a RecyclerView to be docked to the bottom if there is no need to scroll?

后端 未结 4 1174
日久生厌
日久生厌 2021-02-08 17:35

I\'m building a shopping cart RecyclerView that displays all the items in the cart in a RecyclerView, as well as it has an additional view at the bottom that summarizes the cart

相关标签:
4条回答
  • 2021-02-08 17:58

    Building upon Lamorak's answer I've rewritten the code in Kotlin and altered it to accommodate for RecyclerView paddings and item margins:

    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 讨论(0)
  • 2021-02-08 18:00

    Based on the answer by 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;
    }
    
    0 讨论(0)
  • 2021-02-08 18:01

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

    Each time you add an item, you should invalidate item decor offsets so that RecyclerView will call your callback again for a new calculation.

    It won't be trivial but this should work.

    0 讨论(0)
  • 2021-02-08 18:15

    I was thinking about your task and eventually put together some code that you may find useful. There's a problem on this stage though.

    What I did is added an item decorator to a recycler view:

    recyclerView.addItemDecoration(new StickySummaryDecoration());
    

    And here's my implementation of the basic decorator (frankly, it's my first experience with item decorators, so it may not be optimal or totally 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.

    Bottom-docked summary in the short list:

    Short list

    You need to scroll down to see the summary in the long list:

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

    Now about a problem. This side-effect when scrolling back up in the long list is what I haven't solved yet.

    problem

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

    EDIT

    Just now I came to me that the missing row element in the recycler view is my recycled summary view holder that has GONE visibility. There should be a way to bring it back...

    EDIT 2

    I reconsidered my first solution and changed the code a little bit to account for the long list case (in which case the item decoration is disabled (I think it's closer to what you wanted to achieve)), this automatically resolves the missing row problem.

    0 讨论(0)
提交回复
热议问题