2014-10-31 40 views
85

使用新GridLayoutManager:https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.htmlRecyclerView GridLayoutManager:如何自動檢測跨度計數?

這需要一個明確的跨度數,所以現在的問題就變成了:你怎麼知道有多少「跨越」適合每行?畢竟,這是一個網格。根據測量的寬度,RecyclerView可以容納的跨度應該儘可能多。

使用舊的GridView,您只需設置「columnWidth」屬性,它會自動檢測有多少列適合。這基本上就是我會爲RecyclerView複製:

  • 在這個回調添加OnLayoutChangeListener在RecyclerView
  • ,充氣單 '網格項' 和衡量它
  • spanCount = recyclerViewWidth/singleItemWidth;

這似乎是很常見的行爲,所以有沒有更簡單的方法,我沒有看到?

回答

-7

下面是我用來自動檢測跨度計數的包裝的相關部分。您通過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(); 
     } 
    } 
} 
+0

爲什麼贊成接受的答案。應該解釋爲什麼downvote – androidXP 2018-02-03 09:15:36

-1

將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這樣的經理會是最好的解決方案,但是我沒有找到類似的東西。

編輯:有一個bug,一些設備上添加空白空間

+0

右側的空間是不是一個錯誤。如果跨度計數爲5並且'getSpanSize'返回3,則會有空格,因爲您沒有填充跨度。 – 2015-02-27 08:22:29

18

我該使用視圖樹觀察者獲得一次呈現的recylcerview的寬度,然後得到的固定尺寸來實現正確的我從資源中查看卡片視圖,然後在完成我的計算後設置跨度計數。只有當您顯示的項目具有固定寬度時才適用。這幫助我自動填充網格,而不管屏幕大小或方向如何。

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(); 
       } 
      }); 
+2

我用這個,並得到了'ArrayIndexOutOfBoundsException'(*在android.support.v7.widget。GridLayoutManager.layoutChunk(GridLayoutManager.java:361)*)當滾動'RecyclerView'時。 – BornToCode 2014-12-01 10:48:18

+0

只需在setSpanCount()之後添加mLayoutManager.requestLayout()並且它可以工作 – alvinmeimoun 2014-12-04 09:26:36

+1

注意:removeGlobalOnLayoutListener()在API級別16中已棄用。請改用removeOnGlobalLayoutListener()。 [文檔](http://developer.android.com/reference/android/view/ViewTreeObserver.html#removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener))。 – pez 2015-01-20 04:54:46

12

我擴展了RecyclerView並覆蓋了onMeasure方法。

我儘可能早地設置了一個項目寬度(成員變量),默認值爲1.這也更新了配置更改。現在,這將有多達行可容納的肖像,風景,手機/平板電腦等

@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); 
     } 
    } 
} 
+11

+1 Chiu-ki Chan有[博客文章](http://blog.sqisland.com/2014/12/recyclerview-autofit-grid.html)概述了這種方法和[一個示例項目](https://github.com/chiuki/android-recyclerview)。 – CommonsWare 2015-02-06 19:16:13

13

好了,這是我用過,相當基礎,但得到對我所做的工作。這段代碼基本上以蘸一點的方式獲得屏幕寬度,然後除以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); 
+0

爲什麼使用屏幕寬度而不是RecyclerView的寬度?硬編碼300是不好的做法(它需要保持與你的xml佈局同步) – foo64 2015-03-27 06:32:49

+0

@ foo64在xml中,你可以在item上設置match_parent。但是,它仍然難看;) – 2015-12-15 16:40:07

88

本人來說,我不喜歡繼承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); 
    } 
} 

其實我不記得我爲什麼選用設置量程計數onLayoutChildren,我寫了這個類前一段時間。但重要的是我們需要在視圖被測量之後這樣做。所以我們可以得到它的高度和寬度。

編輯:修復代碼錯誤導致錯誤設置跨度計數。感謝用戶@Elyees Abouda報告並建議solution

+8

這應該是被接受的答案,它是'LayoutManager'的工作,使孩子不在'RecyclerView' – mewa 2015-08-27 00:10:19

+0

如果'ColumnWidth'不是固定的。 – Shubham 2015-12-09 07:34:20

+0

@Shubham你是指列寬度根據方向不同而不同的情況? – 2015-12-10 16:54:20

6

我發佈這只是爲了防萬一有人得到奇怪的列寬在我的情況。

由於我的信譽低,我無法對@s-marks的回答發表評論。我申請了他的解決方案solution但我得到了一些奇怪的列寬,所以我修改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固定的問題。

1

爲了適應s-marks答案的方向變化,我添加了一個寬度變化的檢查(寬度從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); 
} 
1

的upvoted的解決方案是好的,但處理傳入的值作爲像素,可以發覺你的,如果你硬編碼值進行測試,並假設DP。最簡單的辦法可能是把列寬的尺寸和配置GridAutofitLayoutManager,它會自動轉換DP到正確的像素值時閱讀:

0
  1. 設置最小的ImageView的固定寬度(144dp X 144dp爲例如)
  2. 當您創建GridLayoutManager,你需要知道有多少列將與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

0

我的結論回答here

相關問題