Android thêm khoảng cách bên dưới phần tử cuối cùng trong chế độ xem lại với bộ điều khiển lưới


84

Tôi đang cố gắng thêm khoảng cách bên dưới hàng phần tử cuối cùng RecyclerViewvới GridLayoutManager. Tôi đã sử dụng tùy chỉnh ItemDecorationcho mục đích này với phần đệm dưới cùng khi phần tử cuối cùng của nó như sau:

public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private int bottomSpace = 0;

public SpaceItemDecoration(int space, int bottomSpace) {
    this.space = space;
    this.bottomSpace = bottomSpace;
}

public SpaceItemDecoration(int space) {
    this.space = space;
    this.bottomSpace = 0;
}

@Override
public void getItemOffsets(Rect outRect, View view,
                           RecyclerView parent, RecyclerView.State state) {

    int childCount = parent.getChildCount();
    final int itemPosition = parent.getChildAdapterPosition(view);
    final int itemCount = state.getItemCount();

    outRect.left = space;
    outRect.right = space;
    outRect.bottom = space;
    outRect.top = space;

    if (itemCount > 0 && itemPosition == itemCount - 1) {
        outRect.bottom = bottomSpace;
    }
}
}

Nhưng vấn đề với phương pháp này là nó làm sai lệch chiều cao của phần tử trong lưới ở hàng cuối cùng. Tôi đoán rằng điều đó sẽ GridLayoutManagerthay đổi chiều cao cho các phần tử dựa trên khoảng cách bên trái. Cách chính xác để đạt được điều này là gì?

Điều này sẽ hoạt động chính xác cho a LinearLayoutManager. Chỉ trong trường hợp GridLayoutManagernó có vấn đề.

Nó rất hữu ích trong trường hợp bạn có một mục FABở dưới cùng và cần các mục ở hàng cuối cùng để cuộn lên trên FABđể chúng có thể hiển thị.

Câu trả lời:


11

Giải pháp cho vấn đề này nằm ở việc ghi đè SpanSizeLookup của GridLayoutManager.

Bạn phải thực hiện các thay đổi đối với GridlayoutManager trong Activity hoặc Fragment nơi bạn đang thổi phồng RecylerView.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //your code 
    recyclerView.addItemDecoration(new PhotoGridMarginDecoration(context));

    // SPAN_COUNT is the number of columns in the Grid View
    GridLayoutManager gridLayoutManager = new GridLayoutManager(context, SPAN_COUNT);

    // With the help of this method you can set span for every type of view
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (list.get(position).getType() == TYPE_HEADER) {
                // Will consume the whole width
                return gridLayoutManager.getSpanCount();
            } else if (list.get(position).getType() == TYPE_CONTENT) {
                // will consume only one part of the SPAN_COUNT
                return 1;
            } else if(list.get(position).getType() == TYPE_FOOTER) {
                // Will consume the whole width
                // Will take care of spaces to be left,
                // if the number of views in a row is not equal to 4
                return gridLayoutManager.getSpanCount();
            }
            return gridLayoutManager.getSpanCount();
        }
    });
    recyclerView.setLayoutManager(gridLayoutManager);
}

436

Chỉ cần thêm một phần đệm và đặt android:clipToPadding="false"

<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="8dp"
    android:clipToPadding="false" />

Cảm ơn câu trả lời tuyệt vời này !


2
Điều này đã làm điều đó cho tôi. Giải pháp nhanh chóng dễ dàng khi bạn cần không gian bằng nhau các vật dụng trong thùng tái chế. Cảm ơn.
Empty2k12,

3
Đây chính xác là những gì tôi đang tìm kiếm! Cảm ơn!
Mariano Zorrilla

1
Giải pháp tuyệt vời và đơn giản. Cảm ơn!
Mikhail

1
Điều này thật tuyệt vời, nhưng nó làm rối tung các cạnh mờ dần, ví dụ như android: RequiFadingEdge = "vertical" hoặc ReclerView.setVerticalFadingEdgeEnabled (true);
Stephan Henningsen

6
Điều này không mở rộng thanh cuộn của chế độ xem trình tái chế xuống dưới cùng. Chỉnh sửa: để ngăn chặn sự bổ sung nàyandroid:scrollbarStyle="outsideOverlay"
Sebastian

8

Nên sử dụng trang trí trong chế độ xem tái chế cho lề dưới trong trường hợp chỉ có mặt hàng cuối cùng

recyclerView.addItemDecoration(MemberItemDecoration())

public class MemberItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // only for the last one
        if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) {
            outRect.bottom = 50/* set your margin here */;
        }
    }
}

6

Tôi đã gặp sự cố tương tự và đã trả lời một luồng khác trong tràn ngăn xếp. Để giúp những người khác truy cập vào trang này, tôi sẽ đăng lại nó ở đây.
Sau khi đọc tất cả các câu trả lời khác và tôi thấy những thay đổi trong bố cục xml cho chế độ tái chế đã hoạt động cho chế độ xem trình tái chế của tôi như mong đợi:

        android:paddingBottom="127px"
        android:clipToPadding="false"
        android:scrollbarStyle="outsideOverlay"  

Bố cục hoàn chỉnh trông giống như:

<android.support.v7.widget.RecyclerView
        android:id="@+id/library_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="160px"
        android:layout_marginEnd="160px"
        tools:listitem="@layout/library_list_item" />  

Để biết tác dụng của trước và sau, hãy xem liên kết tại androidblog.us: Thêm không gian vào cuối Android Recylerview
Hãy cho tôi biết nó hoạt động như thế nào đối với bạn.

David


3

Bạn có thể sử dụng mã bên dưới để phát hiện hàng đầu tiên và hàng cuối cùng trong chế độ xem lưới và đặt hiệu số trên và dưới tương ứng.

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    int pos = params.getViewLayoutPosition();
    int spanCount = mGridLayoutManager.getSpanCount();

    boolean isFirstRow = pos < spanCount;
    boolean isLastRow = state.getItemCount() - 1 - pos < spanCount;

    if (isFirstRow) {
      outRect.top = top offset value here
    }

    if (isLastRow) {
      outRect.bottom = bottom offset value here
    }
}

// you also need to keep reference to GridLayoutManager to know the span count
private final GridLayoutManager mGridLayoutManager;

2

Những gì bạn có thể làm là thêm một chân trang trống vào chế độ xem tái chế của bạn. Phần đệm của bạn sẽ là kích thước của footer.

@Override
public Holder onCreateViewHolder( ViewGroup parent, int viewType) {
    if (viewType == FOOTER) {
        return new FooterHolder();
    }
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    return new Holder(view);
}

@Override
public void onBindViewHolder(final Holder holder, final int position) {
    //if footer
    if (position == items.getSize() - 1) {
    //do nothing
        return;
    }
    //do regular object bindding

}

@Override
public int getItemViewType(int position) {
    return (position == items.getSize() - 1) ? FOOTER : ITEM_VIEW_TYPE_ITEM;
}

@Override
public int getItemCount() {
    //add one for the footer
    return items.size() + 1;
}

Đây là một lựa chọn tốt. Tuy nhiên, vấn đề của tôi với nó là tôi đang sử dụng ItemDecoration tùy chỉnh đã hoạt động dựa trên các vị trí của mục trong chế độ xem lại và quyết định nơi đặt khoảng cách và dải phân cách, v.v. Bây giờ nếu tôi thêm khoảng cách này làm phần tử bổ sung, ItemDecoration của tôi sẽ tính nó như một phần tử và cũng thêm dải phân cách vào khoảng cách. Vì vậy, tôi đang nghĩ nếu có ai đó đã thử sử dụng ItemDecoration tùy chỉnh để giải quyết vấn đề.
pratsJ

Ngoài ra, phương pháp của bạn sẽ hoạt động tốt trong trường hợp LinearLayoutManager hoặc trong trường hợp GridLayoutManager nếu hàng dưới cùng của lưới đã đầy. Nếu không, không gian sẽ đi vào lưới trong không gian phần tử trống và không ở dưới cùng của toàn bộ lưới.
pratsJ

0

Với những thứ như thế này, bạn nên giải quyết bằng cách sử dụng ItemDecoration vì chúng có ý nghĩa như vậy.

public class ListSpacingDecoration extends RecyclerView.ItemDecoration {

  private static final int VERTICAL = OrientationHelper.VERTICAL;

  private int orientation = -1;
  private int spanCount = -1;
  private int spacing;


  public ListSpacingDecoration(Context context, @DimenRes int spacingDimen) {

    spacing = context.getResources().getDimensionPixelSize(spacingDimen);
  }

  public ListSpacingDecoration(int spacingPx) {

    spacing = spacingPx;
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

    super.getItemOffsets(outRect, view, parent, state);

    if (orientation == -1) {
        orientation = getOrientation(parent);
    }

    if (spanCount == -1) {
        spanCount = getTotalSpan(parent);
    }

    int childCount = parent.getLayoutManager().getItemCount();
    int childIndex = parent.getChildAdapterPosition(view);

    int itemSpanSize = getItemSpanSize(parent, childIndex);
    int spanIndex = getItemSpanIndex(parent, childIndex);

    /* INVALID SPAN */
    if (spanCount < 1) return;

    setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex);
  }

  protected void setSpacings(Rect outRect, RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.bottom = spacing;
    }
  }

  @SuppressWarnings("all")
  protected int getTotalSpan(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanSize(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return 1;
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanIndex(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return childIndex % spanCount;
    } else if (mgr instanceof LinearLayoutManager) {
        return 0;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getOrientation(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof LinearLayoutManager) {
        return ((LinearLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getOrientation();
    }

    return VERTICAL;
  }

  protected boolean isBottomEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);

    } else {

        return (spanIndex + itemSpanSize) == spanCount;
    }
  }

  protected boolean isLastItemEdgeValid(boolean isOneOfLastItems, RecyclerView parent, int childCount, int childIndex, int spanIndex) {

    int totalSpanRemaining = 0;
    if (isOneOfLastItems) {
        for (int i = childIndex; i < childCount; i++) {
            totalSpanRemaining = totalSpanRemaining + getItemSpanSize(parent, i);
        }
    }

    return isOneOfLastItems && (totalSpanRemaining <= spanCount - spanIndex);
  }
}

Tôi đã sao chép một câu trả lời đã được chỉnh sửa từ câu trả lời ban đầu của tôi ở đây thực sự là để có khoảng cách bằng nhau nhưng đó là cùng một khái niệm.


0

Bạn có thể lấy DividerItemDecoration.java làm ví dụ từ mã nguồn và thay thế

for (int i = 0; i < childCount; i++)

với

for (int i = 0; i < childCount - 1; i++)

trong drawVertical () và drawHorizontal ()

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.