使用 GridLayoutManager 将项目粘贴到 RecyclerView 的底部
Posted
技术标签:
【中文标题】使用 GridLayoutManager 将项目粘贴到 RecyclerView 的底部【英文标题】:Stick items to bottom inside RecyclerView with GridLayoutManager 【发布时间】:2021-08-18 04:56:18 【问题描述】:我想要一个RecyclerView
,它会像默认情况下那样从左到右、从上到下对其项目进行排序。但另外我想遵循如下图右侧的特殊行为。
我希望项目“作为一个整体”固定在底部,就好像 RecyclerView
本身会在项目不超过 RecyclerViews
高度时调整其大小以包装其内容 - 以便更好地使用拇指用于搜索结果。对于RecyclerView
,我无法通过LayoutManager::reverseLayout
和RecyclerView::layoutDirection
和wrap_content
的组合来获得这种行为,并且将其粘在底部也不起作用,因为我希望整个列表区域都是可滚动的( RecyclerView
必须保持其完整尺寸)- 有什么想法可以让我获得这种行为吗?
【问题讨论】:
您可以使用顶部填充或虚拟条目来填充顶部行。空置的顶部区域显示什么?如您所示,是否有网格图案,还是全部空白?我假设您正在使用 GridLayoutManager。 正确,我使用GridLayoutManager
。网格仅表示,项目以特殊方式粘在底部,RecyclerView
不包装其内容。所以网格最终应该是不可见的。添加虚拟项目确实有效,直到现在我都喜欢这样做 - 但我可能会这样做,因为它也是我想到的唯一解决方案。此外,如果您使用具有不同跨度计数的项目,则虚拟项目的逻辑必须注意这一点,但它仍然有效。
【参考方案1】:
要实现此行为以将所有项目对齐到 RecyclerView 的底部,您需要设置标志 setStackFromEnd(true)
当 stack from bottom 设置为 true 时,列表填充其内容 从视图底部开始
但 GridLayout 不支持此标志,引发异常,例如:
GridLayoutManager 不支持从头开始堆栈。考虑使用 反向布局
但即使在 GridLayoutManager 中使用setReverseLayout(true)
,它也会从下到上和从左到右呈现项目,这不是您想要的结果,甚至可以反转列表的数据。
可能的解决方案:
使用 GridLayoutManager 并仅向第一列添加顶部填充以填充剩余的顶部区域,甚至使用虚拟的空列。
使用带有setStackFromEnd(true)
的LinearLayout 可以按预期工作,其中每个项目从RecyclerView 的底部到顶部呈现,并且每个项目行(项目视图)包含例如:4 个视图(列数),宽度相等。当然,这样列表中的每个 Item 模型必须为每列保存 4 个数据。
如果您选择第二个选项,您可以为每一行准备列数据,如下面的代码 sn-p:
List<ItemModel> data = new ArrayList<>();
int listCnt = 14;
for(int i=0; i<listCnt; i++)
if(i%4 == 0)
String dataCol1 = i < listCnt ? String.valueOf(i) : "";
String dataCol2 = (i + 1) < listCnt ? String.valueOf(i + 1) : "";
String dataCol3 = (i + 2) < listCnt ? String.valueOf(i + 2) : "";
String dataCol4 = (i + 3) < listCnt ? String.valueOf(i + 3) : "";
data.add(new ItemModel(dataCol1, dataCol2, dataCol3, dataCol4));
//set LinearLayoutManager
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
//set adapter
RecyclerViewAdapter adapter = new RecyclerViewAdapter(data);
recyclerView.setAdapter(adapter);
ItemModel 保存每一行的数据,例如:
public class ItemModel
public String dataCol1="", dataCol2="", dataCol3="", dataCol4="";
public ItemModel(String dataCol1, String dataCol2, String dataCol3, String dataCol4)
this.dataCol1 = dataCol1;
this.dataCol2 = dataCol2;
this.dataCol3 = dataCol3;
this.dataCol4 = dataCol4;
当然,在您的适配器中,对于没有任何数据的人,您必须将列 Visibility 设置为 View.INVISIBLE
,对于有数据的人,您必须将列设置为 View.VISIBLE
。
适配器示例如下:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private List<ItemModel> mData;
public RecyclerViewAdapter(List<ItemModel> data)
this.mData = data;
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_row, parent, false);
return new ItemViewHolder(view);
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
ItemModel itemModel = mData.get(position);
if(holder instanceof ItemViewHolder)
((ItemViewHolder) holder).bind(itemModel);
@Override
public int getItemCount()
return mData.size();
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
public TextView itemTextView1, itemTextView2, itemTextView3, itemTextView4;
public ItemViewHolder(View itemView)
super(itemView);
itemTextView1 = itemView.findViewById(R.id.itemTextView1);
itemTextView2 = itemView.findViewById(R.id.itemTextView2);
itemTextView3 = itemView.findViewById(R.id.itemTextView3);
itemTextView4 = itemView.findViewById(R.id.itemTextView4);
itemTextView1.setOnClickListener(this);
itemTextView2.setOnClickListener(this);
itemTextView3.setOnClickListener(this);
itemTextView4.setOnClickListener(this);
public void bind(ItemModel itemModel)
itemTextView1.setText(itemModel.dataCol1);
itemTextView2.setText(itemModel.dataCol2);
itemTextView3.setText(itemModel.dataCol3);
itemTextView4.setText(itemModel.dataCol4);
itemTextView1.setVisibility(TextUtils.isEmpty(itemModel.dataCol1) ? View.INVISIBLE : View.VISIBLE);
itemTextView2.setVisibility(TextUtils.isEmpty(itemModel.dataCol2) ? View.INVISIBLE : View.VISIBLE);
itemTextView3.setVisibility(TextUtils.isEmpty(itemModel.dataCol3) ? View.INVISIBLE : View.VISIBLE);
itemTextView4.setVisibility(TextUtils.isEmpty(itemModel.dataCol4) ? View.INVISIBLE : View.VISIBLE);
@Override
public void onClick(View v)
int cId = v.getId();
if(cId == itemTextView1.getId())
//get col1 data
String dataCol1 = mData.get(getAdapterPosition()).dataCol1;
else if(cId == itemTextView2.getId())
//get col2 data
String dataCol2 = mData.get(getAdapterPosition()).dataCol2;
else if(cId == itemTextView3.getId())
//get col3 data
String dataCol3 = mData.get(getAdapterPosition()).dataCol3;
else if(cId == itemTextView4.getId())
//get col4 data
String dataCol4 = mData.get(getAdapterPosition()).dataCol4;
其中包含 4 列视图的列表项 R.layout.item_list_row
可以如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
android:orientation="horizontal">
<TextView
android:id="@+id/itemTextView1"
android:layout_
android:layout_
android:gravity="center"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/black"
tools:text="1"/>
<TextView
android:id="@+id/itemTextView2"
android:layout_
android:layout_
android:gravity="center"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/black"
tools:text="2"/>
<TextView
android:id="@+id/itemTextView3"
android:layout_
android:layout_
android:gravity="center"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/black"
tools:text="3"/>
<TextView
android:id="@+id/itemTextView4"
android:layout_
android:layout_
android:gravity="center"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/black"
tools:text="4"/>
</LinearLayout>
当然,如果您有不同的跨度计数,您可以在 RecyclerView Adapter 中创建不同的 ViewHolder 类型。
RecyclerView 高度分别为match_parent
、300dp
和wrap_content
的选项 2 的结果将如下所示。 RecyclerView 有灰色背景:
【讨论】:
这只是一个视觉解决方案。手动组合子项目不是一个好的解决方案 - 您将失去默认项目处理、动画等...我更喜欢setReverseLayout
+ 反向数据 + 不可见的虚拟项目,但这也有一些小缺点......到目前为止我的最佳想法。【参考方案2】:
这是我对您正在尝试做的事情的理解。
-
RecyclerView 在屏幕上有一些空间,对于任何给定的设备/方向,宽度和高度都是恒定的。
如果项目计数使其显示填满 RecyclerView 的 屏幕区域,则 RecyclerView 只会填满该区域并随意滚动。
如果项目计数使得 RecyclerView 的 屏幕区域未填满,则项目的整个显示应下降到 RecyclerView 的底部- 屏幕区域在顶部留下空白空间。
执行此操作的一种方法是以编程方式将顶部填充设置为 RecyclerView,即 RecyclerView 的高度与显示项目的总高度之间的差异,如果他们不填充RecyclerView。如果您可以在布局之前计算高度差,这会更容易,但您也可以在布局之后使用global layout listener 计算它。
LinearLayoutManager#findFirstCompletelyVisibleItemPosition() 会告诉您显示屏顶部的项目。
LinearLayoutManager#findLastCompletelyVisibleItemPosition() 会告诉你最后的位置。
如果所有位置都显示出来了,你就知道要引入顶部填充了。
我不清楚如果项目没有填满RecyclerView,你是否希望它们能够滚动到顶部。如果它们应该向上滚动并在底部留下空白空间,请引入底部填充。
您还必须在 RecyclerView 上指定 android:clipToPadding="false"
才能使填充按上述说明工作。
这里有一个示例活动来展示如何做到这一点:
MainActivity.java
public class MainActivity extends AppCompatActivity
private final List<String> mItems = new ArrayList<>();
private RecyclerView mRecycler;
private final int mCount = 24;
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < mCount; i++)
mItems.add("[" + i + "] " + getString(R.string.lorem));
mRecycler = findViewById(R.id.recyclerView);
RecyclerViewAdapter mAdapter = new RecyclerViewAdapter(mItems);
mRecycler.setAdapter(mAdapter);
mRecycler.setLayoutManager(new GridLayoutManager(this, 5));
mRecycler.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
@Override
public void onGlobalLayout()
mRecycler.getViewTreeObserver().removeOnGlobalLayoutListener(this);
GridLayoutManager glm = (GridLayoutManager) mRecycler.getLayoutManager();
if (glm == null) return;
int lastPosition = glm.findLastCompletelyVisibleItemPosition();
if (lastPosition == (mCount - 1))
View lastView = glm.findViewByPosition(lastPosition);
if (lastView == null) return;
int newPadding = mRecycler.getHeight() - lastView.getBottom();
// Set bottom padding to newPadding to allow items to scroll to the top
// of the RecyclerView.
mRecycler.setPadding(0, newPadding, 0, newPadding);
mRecycler.setClipToPadding(false);
);
【讨论】:
感谢您的想法 - 它非常简单,但在我的测试中它不适用于 recyclerview 动画和过滤(计算填充时视图不会位于其最终位置并且动画将不起作用正确)。我认为这只能在自定义布局管理器中正确完成,因此虚拟项目 + 反向数据 + 反向布局似乎仍然是我最好和最简单的选择以上是关于使用 GridLayoutManager 将项目粘贴到 RecyclerView 的底部的主要内容,如果未能解决你的问题,请参考以下文章
如何将 GridLayoutManager 中的项目固定为具有不同跨度计数的父项的开始和结束
如何使用垂直 GridLayoutManager 水平居中 RecyclerView 项目
为啥 RecyclerView 项目在 GridLayoutManager 中随机移动位置?
如何摆脱 GridLayoutManager 项目之间的差距