121

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

明示的なスパン数が必要なため、問題は次のようになります。行ごとにいくつの「スパン」が収まるかをどうやって知るのですか? 結局のところ、これはグリッドです。測定された幅に基づいて、RecyclerView が収まる限り多くのスパンが存在する必要があります。

古い を使用するGridViewと、「columnWidth」プロパティを設定するだけで、適合する列の数が自動的に検出されます。これは基本的に、RecyclerView で複製したいものです。

  • に OnLayoutChangeListener を追加しますRecyclerView
  • このコールバックで、単一の「グリッド アイテム」を膨らませて測定します
  • spanCount = recyclerViewWidth / singleItemWidth;

これはかなり一般的な動作のように思えますが、私が見ていないより簡単な方法はありますか?

4

14 に答える 14

139

個人的には、このために RecyclerView をサブクラス化するのは好きではありません。私にとっては、スパン数を検出する責任が GridLayoutManager にあるように思われるからです。したがって、RecyclerView と GridLayoutManager の Android ソース コードを掘り下げた後、仕事をする独自のクラス拡張 GridLayoutManager を作成しました。

public class GridAutofitLayoutManager extends GridLayoutManager
{
    private int columnWidth;
    private boolean isColumnWidthChanged = true;
    private int lastWidth;
    private int lastHeight;

    public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    public GridAutofitLayoutManager(
        @NonNull final Context context,
        final int columnWidth,
        final int orientation,
        final boolean reverseLayout) {

        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1, orientation, reverseLayout);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    private int checkedColumnWidth(@NonNull final Context context, final 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(final int newColumnWidth) {
        if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
            columnWidth = newColumnWidth;
            isColumnWidthChanged = true;
        }
    }

    @Override
    public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
        final int width = getWidth();
        final int height = getHeight();
        if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
            final int totalSpace;
            if (getOrientation() == VERTICAL) {
                totalSpace = width - getPaddingRight() - getPaddingLeft();
            } else {
                totalSpace = height - getPaddingTop() - getPaddingBottom();
            }
            final int spanCount = Math.max(1, totalSpace / columnWidth);
            setSpanCount(spanCount);
            isColumnWidthChanged = false;
        }
        lastWidth = width;
        lastHeight = height;
        super.onLayoutChildren(recycler, state);
    }
}

onLayoutChildren でスパン カウントを設定することにした理由を実際には覚えていません。このクラスは少し前に書きました。しかし、要点は、ビューが測定された後にそうする必要があるということです。高さと幅を取得できます。

編集 1:スパン数の設定が正しくないために発生したコードのエラーを修正します。解決策を報告し、提案してくれたユーザー@Elyees Aboudaに感謝します。

編集 2:いくつかの小さなリファクタリングと手動の方向変更処理によるエッジ ケースの修正。解決策の報告と提案をしてくれたユーザー@tatarizeに感謝します。

于 2015-05-15T10:12:59.120 に答える
-6

スパン数を自動検出するために使用してきたラッパーの関連部分を次に示します。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();
        }
    }
}
于 2015-03-27T06:23:42.483 に答える