Android 使用 gridlayoutmanager 在 recyclerview 的最后一个元素下方添加间距
Android add spacing below last element in recyclerview with gridlayoutmanager
我正在尝试在 RecyclerView
和 GridLayoutManager
中的最后一个元素行下方添加间距。为此,我使用自定义 ItemDecoration
和底部填充,最后一个元素如下:
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;
}
}
}
但这种方法的问题是它弄乱了最后一行网格中的元素高度。我猜测 GridLayoutManager
会根据左侧间距更改元素的高度。实现此目标的正确方法是什么?
这将适用于 LinearLayoutManager
。以防万一 GridLayoutManager
它有问题。
如果您在底部有一个 FAB
并且需要最后一行的项目滚动到 FAB
上方以便它们可见,这将非常有用。
你可以做的是在你的 recyclerview 中添加一个空的页脚。您的填充将是页脚的大小。
@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;
}
您可以使用下面的代码来检测网格视图中的第一行和最后一行,并相应地设置顶部和底部偏移量。
@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;
解决这个问题的方法是覆盖 GridLayoutManager 的 SpanSizeLookup。
您必须更改 Activity 中的 GridlayoutManager 或您正在膨胀 RecylerView 的 Fragment。
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);
}
像这样的事情,建议使用 ItemDecoration 来解决,因为它们就是为此而设计的。
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);
}
}
我从我的原始答案 中复制了一个编辑过的内容,它实际上是等间距的,但它是相同的概念。
只需添加一个填充并设置android:clipToPadding="false"
<RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:clipToPadding="false" />
多亏了这个精彩answer!
你可以把DividerItemDecoration.java作为源码的例子,替换成
for (int i = 0; i < childCount; i++)
和
for (int i = 0; i < childCount - 1; i++)
在 drawVertical() 和 drawHorizontal() 中
仅在最后一项的情况下,应使用 Recycler View 中的 Decoration 作为底部边距
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 */;
}
}
}
我遇到了类似的问题并回复了 。为了帮助其他登陆此页面的人,我将在此处重新发布。
阅读所有其他回复后,我发现布局更改 xml 的 recyclerview 按预期适用于我的回收站视图:
android:paddingBottom="127px"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
完整的布局如下:
<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" />
查看可能的解决方案,我想写一些笔记和适当的解决方案:
如果你为 RecyclerView 使用填充,它应该在大多数情况下工作,但是一旦你开始使用自定义项目装饰(快速滚动)或处理透明导航栏,你将得到查看各种问题。
您始终可以为 RecyclerView 适配器创建一个 ViewType,它将成为它的页脚,占据整个跨度。这非常有效,但比其他解决方案需要更多的工作。
此处提供的 ItemDecoration 解决方案在大多数情况下都有效,但不适用于 GridLayoutManager,因为它有时会为最后一行上方的行添加间距(此处要求添加更好的解决方案).
我在 android-x 上找到了一些似乎可以解决它的代码,不过,在某种程度上与汽车有关:
- https://androidx.de/androidx/car/widget/itemdecorators/BottomOffsetDecoration.html
- https://androidx.de/androidx/car/util/GridLayoutManagerUtils.html
BottomOffsetDecoration
/**
* A {@link RecyclerView.ItemDecoration} that will add a bottom offset to the last item in the
* RecyclerView it is added to.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class BottomOffsetDecoration extends RecyclerView.ItemDecoration {
private int mBottomOffset;
public BottomOffsetDecoration(int bottomOffset) {
mBottomOffset = bottomOffset;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null || adapter.getItemCount() == 0) {
return;
}
if (parent.getLayoutManager() instanceof GridLayoutManager) {
if (GridLayoutManagerUtils.isOnLastRow(view, parent)) {
outRect.bottom = mBottomOffset;
}
} else if (parent.getChildAdapterPosition(view) == adapter.getItemCount() - 1) {
// Only set the offset for the last item.
outRect.bottom = mBottomOffset;
} else {
outRect.bottom = 0;
}
}
/** Sets the value to use for the bottom offset. */
public void setBottomOffset(int bottomOffset) {
mBottomOffset = bottomOffset;
}
/** Returns the set bottom offset. If none has been set, then 0 will be returned. */
public int getBottomOffset() {
return mBottomOffset;
}
}
GridLayoutManagerUtils
/**
* Utility class that helps navigating in GridLayoutManager.
*
* <p>Assumes parameter {@code RecyclerView} uses {@link GridLayoutManager}.
*
* <p>Assumes the orientation of {@code GridLayoutManager} is vertical.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class GridLayoutManagerUtils {
private GridLayoutManagerUtils() {}
/**
* Returns the number of items in the first row of a RecyclerView that has a
* {@link GridLayoutManager} as its {@code LayoutManager}.
*
* @param recyclerView RecyclerView that uses GridLayoutManager as LayoutManager.
* @return number of items in the first row in {@code RecyclerView}.
*/
public static int getFirstRowItemCount(RecyclerView recyclerView) {
GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
int itemCount = recyclerView.getAdapter().getItemCount();
int spanCount = manager.getSpanCount();
int spanSum = 0;
int numOfItems = 0;
while (numOfItems < itemCount && spanSum < spanCount) {
spanSum += manager.getSpanSizeLookup().getSpanSize(numOfItems);
numOfItems++;
}
return numOfItems;
}
/**
* Returns the span index of an item.
*/
public static int getSpanIndex(View item) {
GridLayoutManager.LayoutParams layoutParams =
((GridLayoutManager.LayoutParams) item.getLayoutParams());
return layoutParams.getSpanIndex();
}
/**
* Returns whether or not the given view is on the last row of a {@code RecyclerView} with a
* {@link GridLayoutManager}.
*
* @param view The view to inspect.
* @param parent {@link RecyclerView} that contains the given view.
* @return {@code true} if the given view is on the last row of the {@code RecyclerView}.
*/
public static boolean isOnLastRow(View view, RecyclerView parent) {
return getLastItemPositionOnSameRow(view, parent) == parent.getAdapter().getItemCount() - 1;
}
/**
* Returns the position of the last item that is on the same row as input {@code view}.
*
* @param view The view to inspect.
* @param parent {@link RecyclerView} that contains the given view.
*/
public static int getLastItemPositionOnSameRow(View view, RecyclerView parent) {
GridLayoutManager layoutManager = ((GridLayoutManager) parent.getLayoutManager());
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int lastItemPosition = parent.getAdapter().getItemCount() - 1;
int currentChildPosition = parent.getChildAdapterPosition(view);
int spanSum = getSpanIndex(view) + spanSizeLookup.getSpanSize(currentChildPosition);
// Iterate to the end of the row starting from the current child position.
while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1);
if (spanSum > spanCount) {
return currentChildPosition;
}
currentChildPosition++;
}
return lastItemPosition;
}
}
所以,我制作了一个 Kotlin 解决方案来解决所有问题(此处提供示例和库):
//https://androidx.de/androidx/car/widget/itemdecorators/BottomOffsetDecoration.html
class BottomOffsetDecoration(private val mBottomOffset: Int, private val layoutManagerType: LayoutManagerType) : ItemDecoration() {
enum class LayoutManagerType {
GRID_LAYOUT_MANAGER, LINEAR_LAYOUT_MANAGER
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
when (layoutManagerType) {
LayoutManagerType.LINEAR_LAYOUT_MANAGER -> {
val position = parent.getChildAdapterPosition(view)
outRect.bottom =
if (state.itemCount <= 0 || position != state.itemCount - 1)
0
else
mBottomOffset
}
LayoutManagerType.GRID_LAYOUT_MANAGER -> {
val adapter = parent.adapter
outRect.bottom =
if (adapter == null || adapter.itemCount == 0 || GridLayoutManagerUtils.isOnLastRow(view, parent))
0
else
mBottomOffset
}
}
}
}
//https://androidx.de/androidx/car/util/GridLayoutManagerUtils.html
/**
* Utility class that helps navigating in GridLayoutManager.
*
*
* Assumes parameter `RecyclerView` uses [GridLayoutManager].
*
*
* Assumes the orientation of `GridLayoutManager` is vertical.
*/
object GridLayoutManagerUtils {
/**
* Returns whether or not the given view is on the last row of a `RecyclerView` with a
* [GridLayoutManager].
*
* @param view The view to inspect.
* @param parent [RecyclerView] that contains the given view.
* @return `true` if the given view is on the last row of the `RecyclerView`.
*/
fun isOnLastRow(view: View, parent: RecyclerView): Boolean {
return getLastItemPositionOnSameRow(view, parent) == parent.adapter!!.itemCount - 1
}
/**
* Returns the position of the last item that is on the same row as input `view`.
*
* @param view The view to inspect.
* @param parent [RecyclerView] that contains the given view.
*/
private fun getLastItemPositionOnSameRow(view: View, parent: RecyclerView): Int {
val layoutManager = parent.layoutManager as GridLayoutManager
val spanSizeLookup = layoutManager.spanSizeLookup
val spanCount = layoutManager.spanCount
val lastItemPosition = parent.adapter!!.itemCount - 1
var currentChildPosition = parent.getChildAdapterPosition(view)
val itemSpanIndex = (view.layoutParams as GridLayoutManager.LayoutParams).spanIndex
var spanSum = itemSpanIndex + spanSizeLookup.getSpanSize(currentChildPosition)
// Iterate to the end of the row starting from the current child position.
while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1)
if (spanSum > spanCount)
return currentChildPosition
++currentChildPosition
}
return lastItemPosition
}
}
<androidx.recyclerview.widget.RecyclerView
...
android:clipToPadding="false"
android:paddingTop="16dp"
android:paddingBottom="16dp"
...
/>
android:clipToPadding="false"
我正在尝试在 RecyclerView
和 GridLayoutManager
中的最后一个元素行下方添加间距。为此,我使用自定义 ItemDecoration
和底部填充,最后一个元素如下:
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;
}
}
}
但这种方法的问题是它弄乱了最后一行网格中的元素高度。我猜测 GridLayoutManager
会根据左侧间距更改元素的高度。实现此目标的正确方法是什么?
这将适用于 LinearLayoutManager
。以防万一 GridLayoutManager
它有问题。
如果您在底部有一个 FAB
并且需要最后一行的项目滚动到 FAB
上方以便它们可见,这将非常有用。
你可以做的是在你的 recyclerview 中添加一个空的页脚。您的填充将是页脚的大小。
@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;
}
您可以使用下面的代码来检测网格视图中的第一行和最后一行,并相应地设置顶部和底部偏移量。
@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;
解决这个问题的方法是覆盖 GridLayoutManager 的 SpanSizeLookup。
您必须更改 Activity 中的 GridlayoutManager 或您正在膨胀 RecylerView 的 Fragment。
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);
}
像这样的事情,建议使用 ItemDecoration 来解决,因为它们就是为此而设计的。
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);
}
}
我从我的原始答案
只需添加一个填充并设置android:clipToPadding="false"
<RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:clipToPadding="false" />
多亏了这个精彩answer!
你可以把DividerItemDecoration.java作为源码的例子,替换成
for (int i = 0; i < childCount; i++)
和
for (int i = 0; i < childCount - 1; i++)
在 drawVertical() 和 drawHorizontal() 中
仅在最后一项的情况下,应使用 Recycler View 中的 Decoration 作为底部边距
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 */;
}
}
}
我遇到了类似的问题并回复了
阅读所有其他回复后,我发现布局更改 xml 的 recyclerview 按预期适用于我的回收站视图:
android:paddingBottom="127px"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
完整的布局如下:
<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" />
查看可能的解决方案,我想写一些笔记和适当的解决方案:
如果你为 RecyclerView 使用填充,它应该在大多数情况下工作,但是一旦你开始使用自定义项目装饰(快速滚动)或处理透明导航栏,你将得到查看各种问题。
您始终可以为 RecyclerView 适配器创建一个 ViewType,它将成为它的页脚,占据整个跨度。这非常有效,但比其他解决方案需要更多的工作。
此处提供的 ItemDecoration 解决方案在大多数情况下都有效,但不适用于 GridLayoutManager,因为它有时会为最后一行上方的行添加间距(此处要求添加更好的解决方案).
我在 android-x 上找到了一些似乎可以解决它的代码,不过,在某种程度上与汽车有关:
- https://androidx.de/androidx/car/widget/itemdecorators/BottomOffsetDecoration.html
- https://androidx.de/androidx/car/util/GridLayoutManagerUtils.html
BottomOffsetDecoration
/**
* A {@link RecyclerView.ItemDecoration} that will add a bottom offset to the last item in the
* RecyclerView it is added to.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class BottomOffsetDecoration extends RecyclerView.ItemDecoration {
private int mBottomOffset;
public BottomOffsetDecoration(int bottomOffset) {
mBottomOffset = bottomOffset;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null || adapter.getItemCount() == 0) {
return;
}
if (parent.getLayoutManager() instanceof GridLayoutManager) {
if (GridLayoutManagerUtils.isOnLastRow(view, parent)) {
outRect.bottom = mBottomOffset;
}
} else if (parent.getChildAdapterPosition(view) == adapter.getItemCount() - 1) {
// Only set the offset for the last item.
outRect.bottom = mBottomOffset;
} else {
outRect.bottom = 0;
}
}
/** Sets the value to use for the bottom offset. */
public void setBottomOffset(int bottomOffset) {
mBottomOffset = bottomOffset;
}
/** Returns the set bottom offset. If none has been set, then 0 will be returned. */
public int getBottomOffset() {
return mBottomOffset;
}
}
GridLayoutManagerUtils
/**
* Utility class that helps navigating in GridLayoutManager.
*
* <p>Assumes parameter {@code RecyclerView} uses {@link GridLayoutManager}.
*
* <p>Assumes the orientation of {@code GridLayoutManager} is vertical.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class GridLayoutManagerUtils {
private GridLayoutManagerUtils() {}
/**
* Returns the number of items in the first row of a RecyclerView that has a
* {@link GridLayoutManager} as its {@code LayoutManager}.
*
* @param recyclerView RecyclerView that uses GridLayoutManager as LayoutManager.
* @return number of items in the first row in {@code RecyclerView}.
*/
public static int getFirstRowItemCount(RecyclerView recyclerView) {
GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
int itemCount = recyclerView.getAdapter().getItemCount();
int spanCount = manager.getSpanCount();
int spanSum = 0;
int numOfItems = 0;
while (numOfItems < itemCount && spanSum < spanCount) {
spanSum += manager.getSpanSizeLookup().getSpanSize(numOfItems);
numOfItems++;
}
return numOfItems;
}
/**
* Returns the span index of an item.
*/
public static int getSpanIndex(View item) {
GridLayoutManager.LayoutParams layoutParams =
((GridLayoutManager.LayoutParams) item.getLayoutParams());
return layoutParams.getSpanIndex();
}
/**
* Returns whether or not the given view is on the last row of a {@code RecyclerView} with a
* {@link GridLayoutManager}.
*
* @param view The view to inspect.
* @param parent {@link RecyclerView} that contains the given view.
* @return {@code true} if the given view is on the last row of the {@code RecyclerView}.
*/
public static boolean isOnLastRow(View view, RecyclerView parent) {
return getLastItemPositionOnSameRow(view, parent) == parent.getAdapter().getItemCount() - 1;
}
/**
* Returns the position of the last item that is on the same row as input {@code view}.
*
* @param view The view to inspect.
* @param parent {@link RecyclerView} that contains the given view.
*/
public static int getLastItemPositionOnSameRow(View view, RecyclerView parent) {
GridLayoutManager layoutManager = ((GridLayoutManager) parent.getLayoutManager());
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int lastItemPosition = parent.getAdapter().getItemCount() - 1;
int currentChildPosition = parent.getChildAdapterPosition(view);
int spanSum = getSpanIndex(view) + spanSizeLookup.getSpanSize(currentChildPosition);
// Iterate to the end of the row starting from the current child position.
while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1);
if (spanSum > spanCount) {
return currentChildPosition;
}
currentChildPosition++;
}
return lastItemPosition;
}
}
所以,我制作了一个 Kotlin 解决方案来解决所有问题(此处提供示例和库):
//https://androidx.de/androidx/car/widget/itemdecorators/BottomOffsetDecoration.html
class BottomOffsetDecoration(private val mBottomOffset: Int, private val layoutManagerType: LayoutManagerType) : ItemDecoration() {
enum class LayoutManagerType {
GRID_LAYOUT_MANAGER, LINEAR_LAYOUT_MANAGER
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
when (layoutManagerType) {
LayoutManagerType.LINEAR_LAYOUT_MANAGER -> {
val position = parent.getChildAdapterPosition(view)
outRect.bottom =
if (state.itemCount <= 0 || position != state.itemCount - 1)
0
else
mBottomOffset
}
LayoutManagerType.GRID_LAYOUT_MANAGER -> {
val adapter = parent.adapter
outRect.bottom =
if (adapter == null || adapter.itemCount == 0 || GridLayoutManagerUtils.isOnLastRow(view, parent))
0
else
mBottomOffset
}
}
}
}
//https://androidx.de/androidx/car/util/GridLayoutManagerUtils.html
/**
* Utility class that helps navigating in GridLayoutManager.
*
*
* Assumes parameter `RecyclerView` uses [GridLayoutManager].
*
*
* Assumes the orientation of `GridLayoutManager` is vertical.
*/
object GridLayoutManagerUtils {
/**
* Returns whether or not the given view is on the last row of a `RecyclerView` with a
* [GridLayoutManager].
*
* @param view The view to inspect.
* @param parent [RecyclerView] that contains the given view.
* @return `true` if the given view is on the last row of the `RecyclerView`.
*/
fun isOnLastRow(view: View, parent: RecyclerView): Boolean {
return getLastItemPositionOnSameRow(view, parent) == parent.adapter!!.itemCount - 1
}
/**
* Returns the position of the last item that is on the same row as input `view`.
*
* @param view The view to inspect.
* @param parent [RecyclerView] that contains the given view.
*/
private fun getLastItemPositionOnSameRow(view: View, parent: RecyclerView): Int {
val layoutManager = parent.layoutManager as GridLayoutManager
val spanSizeLookup = layoutManager.spanSizeLookup
val spanCount = layoutManager.spanCount
val lastItemPosition = parent.adapter!!.itemCount - 1
var currentChildPosition = parent.getChildAdapterPosition(view)
val itemSpanIndex = (view.layoutParams as GridLayoutManager.LayoutParams).spanIndex
var spanSum = itemSpanIndex + spanSizeLookup.getSpanSize(currentChildPosition)
// Iterate to the end of the row starting from the current child position.
while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1)
if (spanSum > spanCount)
return currentChildPosition
++currentChildPosition
}
return lastItemPosition
}
}
<androidx.recyclerview.widget.RecyclerView
...
android:clipToPadding="false"
android:paddingTop="16dp"
android:paddingBottom="16dp"
...
/>
android:clipToPadding="false"