如果不需要滚动,是否可以将RecyclerView中的最后一项停靠在底部?

时间:2015-04-14 21:26:57

标签: android android-recyclerview

我正在构建一个购物车RecyclerView,它在RecyclerView中显示购物车中的所有商品,并在底部有一个额外的视图,用于汇总购物车(如果适用,总额,优惠券折扣等) )。

如果有>购物车中的3个项目看起来很好,因为用户必须滚动到底部才能查看"摘要视图"。但是,如果有1个或2个项目,则首先显示的是项目,而不是摘要视图,然后是空格。我更喜欢第一项,然后是空白,然后是摘要视图。

我尝试添加空项,但是,根据设备的分辨率,它看起来不一致。

当前外观少于3件(即如果不需要滚动):

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

期望的外表:

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

4 个答案:

答案 0 :(得分:3)

基于the answer by yigit我创建了一个有效的实现。扩展ItemDecoration并覆盖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;
}

答案 1 :(得分:2)

这不是一个预期的用例,但你创建一个ItemDecorator,当在底部视图上调用getItemOffsets时,计算它上面的空间并将其设置为decorOffsetTop。

每次添加项目时,都应使项目装饰偏移无效,以便RecyclerView再次调用您的回调以进行新计算。

这不会是微不足道的,但这应该有效。

答案 2 :(得分:2)

我正在考虑你的任务,并最终整理了一些你可能觉得有用的代码。但是在这个舞台上有一个问题。

我所做的是将项目装饰器添加到回收者视图中:

recyclerView.addItemDecoration(new StickySummaryDecoration());

这是我对基本装饰器的实现(坦率地说,这是我对项目装饰器的第一次体验,所以它可能不是最佳或完全正确但我尽了最大努力):

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);
        }
    }
}

所以我得到了这个。

短名单中的底部停靠摘要:

Short list

您需要向下滚动才能看到长列表中的摘要:

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

现在关于一个问题。在长列表中向后滚动时的这种副作用是我尚未解决的问题。

problem

我的实验上传到github repo以防万一:)

修改

刚才我找到了回收站视图中缺少的行元素是我的可回收摘要视图持有者,其GONE可见性。应该有办法把它带回来......

编辑2

我重新考虑了我的第一个解决方案并稍微更改了代码来解释长列表情况(在这种情况下,项目装饰被禁用(我认为它更接近你想要达到的目的)),这会自动解决丢失的问题排问题。

答案 3 :(得分:0)

基于Lamorak的答案,我用Kotlin重写了代码,并对其进行了更改,以适应RecyclerView填充和项目边距:

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
        }
}