RecyclerView GridLayoutManager:如何自动检测跨度计数?

使用新的GridLayoutManager: https : //developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html

它需要一个明确的跨度计数,所以现在的问题变成:你怎么知道每行有多less“跨度”? 毕竟,这是一个网格。 根据测量的宽度,应该有RecyclerView可以容纳的跨度。

使用旧的GridView,你只需设置“columnWidth”属性,它会自动检测有多less列。 这基本上是我会为RecyclerView复制:

  • 在RecyclerView上添加OnLayoutChangeListener
  • 在这个callback中,膨胀单个“网格项”并测量它
  • spanCount = recyclerViewWidth / singleItemWidth;

这似乎是相当普遍的行为,所以有一个更简单的方法,我没有看到?

Personaly我不喜欢RecyclerView的子类,因为对于我来说,似乎有GridLayoutManager的责任来检测跨度计数。 所以在一些Android源代码挖掘RecyclerView和GridLayoutManager之后,我编写了自己的类扩展GridLayoutManager来完成这项工作:

public class GridAutofitLayoutManager extends GridLayoutManager { private int mColumnWidth; private boolean mColumnWidthChanged = true; public GridAutofitLayoutManager(Context context, int columnWidth) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1); setColumnWidth(checkedColumnWidth(context, columnWidth)); } public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1, orientation, reverseLayout); setColumnWidth(checkedColumnWidth(context, columnWidth)); } private int checkedColumnWidth(Context context, int columnWidth) { if (columnWidth <= 0) { /* Set default columnWidth value (48dp here). It is better to move this constant to static constant on top, but we need context to convert it to dp, so can't really do so. */ columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); } return columnWidth; } public void setColumnWidth(int newColumnWidth) { if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) { mColumnWidth = newColumnWidth; mColumnWidthChanged = true; } } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int width = getWidth(); int height = getHeight(); if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = width - getPaddingRight() - getPaddingLeft(); } else { totalSpace = height - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / mColumnWidth); setSpanCount(spanCount); mColumnWidthChanged = false; } super.onLayoutChildren(recycler, state); } } 

我实际上并不记得为什么我select在onLayoutChildren中设置跨度数,我前一段时间写了这个类。 但重点是我们需要这样做后视图得到测量。 所以我们可以得到它的高度和宽度。

编辑:修复代码错误导致错误地设置跨度计数错误。 感谢用户@Elyees Abouda的报告和build议解决scheme 。

我使用视图树观察器完成了此操作,以获取一次呈现的recylcerview的宽度,然后从资源中获取我的卡片视图的固定尺寸,然后在完成我的计算后设置跨度数。 只有当您显示的项目具有固定宽度时才适用。 这帮助我自动填充网格,不pipe屏幕大小或方向。

 mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mRecyclerView.getViewTreeObserver().removeOnGLobalLayoutListener(this); int viewWidth = mRecyclerView.getMeasuredWidth(); float cardViewWidth = getActivity().getResources().getDimension(R.dimen.cardview_layout_width); int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth); mLayoutManager.setSpanCount(newSpanCount); mLayoutManager.requestLayout(); } }); 

我扩展了RecyclerView,并覆盖了onMeasure方法。

我尽可能早地设置了项目宽度(成员variables),默认值为1.这也更新了configuration更改。 现在,它将具有纵向,横向,手机/平板电脑等所能容纳的行数。

 @Override protected void onMeasure(int widthSpec, int heightSpec) { super.onMeasure(widthSpec, heightSpec); int width = MeasureSpec.getSize(widthSpec); if(width != 0){ int spans = width / mItemWidth; if(spans > 0){ mLayoutManager.setSpanCount(spans); } } } 

那么,这是我用的,相当基本的,但为我完成工作。 这段代码基本上是在蘸一下屏幕宽度,然后除以300(或适配器的布局使用任何宽度)。 因此,300-500的宽度较小的手机只能显示一列,2-3列的数据等。简而言之,我们可以看到,没有任何缺点。

 Display display = getActivity().getWindowManager().getDefaultDisplay(); DisplayMetrics outMetrics = new DisplayMetrics(); display.getMetrics(outMetrics); float density = getResources().getDisplayMetrics().density; float dpWidth = outMetrics.widthPixels / density; int columns = Math.round(dpWidth/300); mLayoutManager = new GridLayoutManager(getActivity(),columns); mRecyclerView.setLayoutManager(mLayoutManager); 

我发布这个,以防万一有人得到奇怪的列宽在我的情况。

由于我的名誉低,我不能评论@ s-marks的答案。 我应用他的解决scheme的解决scheme,但我有一些奇怪的列宽,所以我修改checkedColumnWidth函数如下:

 private int checkedColumnWidth(Context context, int columnWidth) { if (columnWidth <= 0) { /* Set default columnWidth value (48dp here). It is better to move this constant to static constant on top, but we need context to convert it to dp, so can't really do so. */ columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); } else { columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth, context.getResources().getDisplayMetrics()); } return columnWidth; } 

通过将给定的列宽转换为DP来解决问题。

为了适应s标记的答案的方向改变,我添加了一个宽度改变的检查(宽度从getWidth(),而不是列的宽度)。

 private boolean mWidthChanged = true; private int mWidth; @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int width = getWidth(); int height = getHeight(); if (width != mWidth) { mWidthChanged = true; mWidth = width; } if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0 || mWidthChanged) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = width - getPaddingRight() - getPaddingLeft(); } else { totalSpace = height - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / mColumnWidth); setSpanCount(spanCount); mColumnWidthChanged = false; mWidthChanged = false; } super.onLayoutChildren(recycler, state); } 

upvoted的解决scheme是好的,但处理传入的值作为像素,如果你硬编码值testing和假设dp可以让你起来。 最简单的方法可能是将列宽放在一个维度中,并在configurationGridAutofitLayoutManager时读取它,它会自动将dp转换为正确的像素值:

 new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width)) 
  1. 设置imageView的最小固定宽度(例如144dp x 144dp)
  2. 当你创buildGridLayoutManager时,你需要知道有多less列与最小的imageView的大小:

     WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); //Получаем размер экрана Display display = wm.getDefaultDisplay(); Point point = new Point(); display.getSize(point); int screenWidth = point.x; //Ширина экрана int photoWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 144, this.getResources().getDisplayMetrics()); //Переводим в точки int columnsCount = screenWidth/photoWidth; //Число столбцов GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columnsCount); recyclerView.setLayoutManager(gridLayoutManager); 
  3. 之后,如果列中有空格,则需要在适配器中调整imageView的大小。 你可以发送newImageViewSize,然后从那里的活动inisilize适配器计算屏幕和列数:

     @Override //Заполнение нашей плитки public void onBindViewHolder(PhotoHolder holder, int position) { ... ViewGroup.LayoutParams photoParams = holder.photo.getLayoutParams(); //Параметры нашей фотографии int newImageViewSize = screenWidth/columnsCount; //Новый размер фотографии photoParams.width = newImageViewSize; //Установка нового размера photoParams.height = newImageViewSize; holder.photo.setLayoutParams(photoParams); //Установка параметров ... } 

它在两个方向工作。 在垂直方向上,我有2列和横向 – 4列。 结果是: https : //i.stack.imgur.com/WHvyD.jpg

我在这里得出的结论如下

将spanCount设置为一个很大的数字(这是最大列数)并将一个自定义SpanSizeLookup设置为GridLayoutManager。

 mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int i) { return SPAN_COUNT / (int) (mRecyclerView.getMeasuredWidth()/ CELL_SIZE_IN_PX); } }); 

这有点难看,但它工作。

我认为像AutoSpanGridLayoutManager这样的经理将是最好的解决scheme,但我没有find这样的事情。

编辑:有一个错误,在一些设备上,它增加了空白的权利

这里是我用来自动检测跨度计数的包装的相关部分。 您可以通过使用R.layout.my_grid_item引用调用setGridLayoutManager初始化它,并且可以计算出每行中可以容纳的数量。

 public class AutoSpanRecyclerView extends RecyclerView { private int m_gridMinSpans; private int m_gridItemLayoutId; private LayoutRequester m_layoutRequester = new LayoutRequester(); public void setGridLayoutManager( int orientation, int itemLayoutId, int minSpans ) { GridLayoutManager layoutManager = new GridLayoutManager( getContext(), 2, orientation, false ); m_gridItemLayoutId = itemLayoutId; m_gridMinSpans = minSpans; setLayoutManager( layoutManager ); } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom ) { super.onLayout( changed, left, top, right, bottom ); if( changed ) { LayoutManager layoutManager = getLayoutManager(); if( layoutManager instanceof GridLayoutManager ) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; LayoutInflater inflater = LayoutInflater.from( getContext() ); View item = inflater.inflate( m_gridItemLayoutId, this, false ); int measureSpec = View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED ); item.measure( measureSpec, measureSpec ); int itemWidth = item.getMeasuredWidth(); int recyclerViewWidth = getMeasuredWidth(); int spanCount = Math.max( m_gridMinSpans, recyclerViewWidth / itemWidth ); gridLayoutManager.setSpanCount( spanCount ); // if you call requestLayout() right here, you'll get ArrayIndexOutOfBoundsException when scrolling post( m_layoutRequester ); } } } private class LayoutRequester implements Runnable { @Override public void run() { requestLayout(); } } }