Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
434 views
in Technique[技术] by (71.8m points)

android - How to design spannable gridview using recyclerview (SpannableGridLayoutManager)

I want to design grid view like below image provided. The first item should be bigger than the rest.

enter image description here

Currently I am using RelativeLayout with GridLayoutManager check below code

RecyclerView recyclerView = (RecyclerView) 
findViewById(R.id.recycler_view1);    
RecyclerView.LayoutManager  recyclerViewLayoutManager = new 
GridLayoutManager(context, 3);

recyclerView.setLayoutManager(recyclerViewLayoutManager);
recyclerView_Adapter = new RecyclerViewAdapter(context,numbers);
recyclerView.setAdapter(recyclerView_Adapter);

Dummy array for demo

String[] numbers = {
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight",
        "nine",
        "ten",
        "eleven",

};

Adapter Class

public class RecyclerViewAdapter extends 
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>{

String[] values;
Context context1;

public RecyclerViewAdapter(Context context2,String[] values2){

    values = values2;

    context1 = context2;
}

public static class ViewHolder extends RecyclerView.ViewHolder{

    public TextView textView;

    public ViewHolder(View v){

        super(v);

        textView = (TextView) v.findViewById(R.id.textview1);

    }
}

@Override
public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){

    View view1 = LayoutInflater.from(context1).inflate(R.layout.recycler_view_items,parent,false);

    ViewHolder viewHolder1 = new ViewHolder(view1);

    return viewHolder1;
}

@Override
public void onBindViewHolder(ViewHolder Vholder, int position){

    Vholder.textView.setText(values[position]);

    Vholder.textView.setBackgroundColor(Color.CYAN);

    Vholder.textView.setTextColor(Color.BLUE);

}

@Override
public int getItemCount(){

    return values.length;
} }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I have implemented the SpannableGridLayoutManager for this and its working perfectly for me. Please check the following solution.

Activity Code

SpannableGridLayoutManager gridLayoutManager = new 
SpannableGridLayoutManager(new SpannableGridLayoutManager.GridSpanLookup() {
        @Override
        public SpannableGridLayoutManager.SpanInfo getSpanInfo(int position) 
        {
            if (position == 0) {
                return new SpannableGridLayoutManager.SpanInfo(2, 2);
                //this will count of row and column you want to replace
            } else {
                return new SpannableGridLayoutManager.SpanInfo(1, 1);
            }
        }
    }, 3, 1f); // 3 is the number of coloumn , how nay to display is 1f

    recyclerView.setLayoutManager(gridLayoutManager);

In adapter write following Code

public MyViewHolder(View itemView) {
     super(itemView);
     GridLayoutManager.LayoutParams layoutParams = new 
     GridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
     ViewGroup.LayoutParams.MATCH_PARENT);
     float margin = DimensionUtils.convertDpToPixel(5);
     layoutParams.setMargins((int) margin, (int) margin, (int) margin, 
     (int) margin);
     itemView.setLayoutParams(layoutParams);
    }

SpannableGridLayoutManager custom class

public class SpannableGridLayoutManager extends RecyclerView.LayoutManager {

private GridSpanLookup spanLookup;
private int columns = 1;
private float cellAspectRatio = 1f;

private int cellHeight;
private int[] cellBorders;
private int firstVisiblePosition;
private int lastVisiblePosition;
private int firstVisibleRow;
private int lastVisibleRow;
private boolean forceClearOffsets;
private SparseArray<GridCell> cells;
private List<Integer> firstChildPositionForRow; // key == row, val == first child position
private int totalRows;
private final Rect itemDecorationInsets = new Rect();

public SpannableGridLayoutManager(GridSpanLookup spanLookup, int columns, float cellAspectRatio) {
    this.spanLookup = spanLookup;
    this.columns = columns;
    this.cellAspectRatio = cellAspectRatio;
    setAutoMeasureEnabled(true);
}

@Keep /* XML constructor, see RecyclerView#createLayoutManager */
public SpannableGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SpannableGridLayoutManager, defStyleAttr, defStyleRes);
    columns = a.getInt(R.styleable.SpannableGridLayoutManager_android_orientation, 1);
    parseAspectRatio(a.getString(R.styleable.SpannableGridLayoutManager_aspectRatio));
    int orientation = a.getInt(R.styleable.SpannableGridLayoutManager_android_orientation, RecyclerView.VERTICAL);

    a.recycle();
    setAutoMeasureEnabled(true);
}

public interface GridSpanLookup {
    SpanInfo getSpanInfo(int position);
}

public void setSpanLookup(@NonNull GridSpanLookup spanLookup) {
    this.spanLookup = spanLookup;
}

public static class SpanInfo {
    public int columnSpan;
    public int rowSpan;

    public SpanInfo(int columnSpan, int rowSpan) {
        this.columnSpan = columnSpan;
        this.rowSpan = rowSpan;
    }

    public static final SpanInfo SINGLE_CELL = new SpanInfo(1, 1);
}

public static class LayoutParams extends RecyclerView.LayoutParams {

    int columnSpan;
    int rowSpan;

    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    public LayoutParams(ViewGroup.MarginLayoutParams source) {
        super(source);
    }

    public LayoutParams(ViewGroup.LayoutParams source) {
        super(source);
    }

    public LayoutParams(RecyclerView.LayoutParams source) {
        super(source);
    }
}

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    calculateWindowSize();
    calculateCellPositions(recycler, state);

    if (state.getItemCount() == 0) {
        detachAndScrapAttachedViews(recycler);
        firstVisibleRow = 0;
        resetVisibleItemTracking();
        return;
    }

    // TODO use orientationHelper
    int startTop = getPaddingTop();
    int scrollOffset = 0;
    if (forceClearOffsets) { // see #scrollToPosition
        startTop = -(firstVisibleRow * cellHeight);
        forceClearOffsets = false;
    } else if (getChildCount() != 0) {
        scrollOffset = getDecoratedTop(getChildAt(0));
        startTop = scrollOffset - (firstVisibleRow * cellHeight);
        resetVisibleItemTracking();
    }

    detachAndScrapAttachedViews(recycler);
    int row = firstVisibleRow;
    int availableSpace = getHeight() - scrollOffset;
    int lastItemPosition = state.getItemCount() - 1;
    while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) {
        availableSpace -= layoutRow(row, startTop, recycler, state);
        row = getNextSpannedRow(row);
    }

    layoutDisappearingViews(recycler, state, startTop);
}

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
}

@Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
    return new LayoutParams(c, attrs);
}

@Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
    if (lp instanceof ViewGroup.MarginLayoutParams) {
        return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
    } else {
        return new LayoutParams(lp);
    }
}

@Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
    return lp instanceof LayoutParams;
}

@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
    removeAllViews();
    reset();
}

@Override
public boolean supportsPredictiveItemAnimations() {
    return true;
}

@Override
public boolean canScrollVertically() {
    return true;
}

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || dy == 0) return 0;

    int scrolled;
    int top = getDecoratedTop(getChildAt(0));

    if (dy < 0) { // scrolling content down
        if (firstVisibleRow == 0) { // at top of content
            int scrollRange = -(getPaddingTop() - top);
            scrolled = Math.max(dy, scrollRange);
        } else {
            scrolled = dy;
        }
        if (top - scrolled >= 0) { // new top row came on screen
            int newRow = firstVisibleRow - 1;
            if (newRow >= 0) {
                int startOffset = top - (firstVisibleRow * cellHeight);
                layoutRow(newRow, startOffset, recycler, state);
            }
        }
        int firstPositionOfLastRow = getFirstPositionInSpannedRow(lastVisibleRow);
        int lastRowTop = getDecoratedTop(
                getChildAt(firstPositionOfLastRow - firstVisiblePosition));
        if (lastRowTop - scrolled > getHeight()) { // last spanned row scrolled out
            recycleRow(lastVisibleRow, recycler, state);
        }
    } else { // scrolling content up
        int bottom = getDecoratedBottom(getChildAt(getChildCount() - 1));
        if (lastVisiblePosition == getItemCount() - 1) { // is at end of content
            int scrollRange = Math.max(bottom - getHeight() + getPaddingBottom(), 0);
            scrolled = Math.min(dy, scrollRange);
        } else {
            scrolled = dy;
        }
        if ((bottom - scrolled) < getHeight()) { // new row scrolled in
            int nextRow = lastVisibleRow + 1;
            if (nextRow < getSpannedRowCount()) {
                int startOffset = top - (firstVisibleRow * cellHeight);
                layoutRow(nextRow, startOffset, recycler, state);
            }
        }
        int lastPositionInRow = getLastPositionInSpannedRow(firstVisibleRow, state);
        int bottomOfFirstRow =
                getDecoratedBottom(getChildAt(lastPositionInRow - firstVisiblePosition));
        if (bottomOfFirstRow - scrolled < 0) { // first spanned row scrolled out
            recycleRow(firstVisibleRow, recycler, state);
        }
    }
    offsetChildrenVertical(-scrolled);
    return scrolled;
}

@Override
public void scrollToPosition(int position) {
    if (position >= getItemCount()) position = getItemCount() - 1;

    firstVisibleRow = getRowIndex(position);
    resetVisibleItemTracking();
    forceClearOffsets = true;
    removeAllViews();
    requestLayout();
}

@Override
public void smoothScrollToPosition(
        RecyclerView recyclerView, RecyclerView.State state, int position) {
    if (position >= getItemCount()) position = getItemCount() - 1;

    LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            final int rowOffset = getRowIndex(targetPosition) - firstVisibleRow;
            return new PointF(0, rowOffset * cellHeight);
        }
    };
    scroller.setTargetPosition(position);
    startSmoothScroll(scroller);
}

@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
    // TODO update this to incrementally calculate
    return getSpannedRowCount() * cellHeight + getPaddingTop() + getPaddingBottom();
}

@Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
    return getHeight();
}

@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
    if (getChildCount() == 0) return 0;
    return getPaddingTop() + (firstVisibleRow * cellHeight) - getDecoratedTop(getChildAt(0));
}

@Override
public View findViewByPosition(int position) {
    if (position < firstVisiblePosition || position > lastVisiblePosition) return null;
    return getChildAt(position - firstVisiblePosition);
}

public int getFirstVisibleItemPosition() {
    return firstVisiblePosition;
}

private static class GridCell {
    final int row;
    final int rowSpan;
    final int column;
    final int columnSpan;

    GridCell(int row, int rowSpan, int column, int columnSpan) {
        this.row = row;
        this.rowSpan = rowSpan;
        this.column = column;
        this.columnSpan = columnSpan;
    }
}

/**
 * This is the main layout algorithm, iterates over all items and places them into [column, row]
 * cell positions. Stores this layout info for use later on. Also records the adapter position
 * that each row starts at.
 * <p>
 * Note that if a row is spanned, then the row start position is recorded as the first cell of
 * the row that the spanned cell starts in. This is to ensure that we have sufficient contiguous
 * views to layout/draw a spanned row.
 */
private void calculateCellPositions(RecyclerView.Recycler recycler, RecyclerView.State state) {
    final int itemCount = state.getItemCount();
    cells = new SparseArray<>(itemCount);
    firstChildPositionForRow = new ArrayList<>();
    int row = 0;
    int column = 0;
    recordSpannedRowStartPosition(row, column);
    int[] rowHWM = new int[columns]; // row high water mark (per column)
    for (int position = 0; position < itemCount; position++) {

        SpanInfo spanInfo;
        int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(positio

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...