2015-03-19 19 views
26

Jaki jest najlepszy i najłatwiejszy sposób dekorowania RecyclerView, aby mieć taki wygląd?Dekorowanie RecyclerView (z GridLayoutManager), aby wyświetlić dzielnik między pozycjami

enter image description here

Głównym wyzwaniem jest posiadanie dzielniki tylko pomiędzy pozycjami, ale nie pomiędzy elementami oraz lewy/prawy granice ekranu.

Wszelkie pomysły?

+0

zobaczyć https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, android.view .View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State) – pskink

+1

@pskink Widzę, że konieczne jest stworzenie/ponowne użycie jakiegoś elementu dekoracji. Pytanie dotyczy konkretnego rozwiązania. – AlexKorovyansky

+0

@pskink jaki jest prawidłowy sposób określenia, czy element ma prawe/lewe sąsiednie, czy też nie ma metody getItemOffsets? – AlexKorovyansky

Odpowiedz

76

Nie wiem, dlaczego tego potrzebujesz, ale ten interfejs użytkownika jest dość łatwy do wdrożenia dzięki dekoratorowi RecyclerView.

mRecylerView.addItemDecoration(new ItemDecorationAlbumColumns(
    getResources().getDimensionPixelSize(R.dimen.photos_list_spacing), 
    getResources().getInteger(R.integer.photo_list_preview_columns))); 

i dekorator (potrzebuje refatoring)

import android.graphics.Rect; 
import android.support.v7.widget.RecyclerView; 
import android.view.View; 

public class ItemDecorationAlbumColumns extends RecyclerView.ItemDecoration { 

    private int mSizeGridSpacingPx; 
    private int mGridSize; 

    private boolean mNeedLeftSpacing = false; 

    public ItemDecorationAlbumColumns(int gridSpacingPx, int gridSize) { 
     mSizeGridSpacingPx = gridSpacingPx; 
     mGridSize = gridSize; 
    } 

    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
     int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1))/mGridSize); 
     int padding = parent.getWidth()/mGridSize - frameWidth; 
     int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition(); 
     if (itemPosition < mGridSize) { 
      outRect.top = 0; 
     } else { 
      outRect.top = mSizeGridSpacingPx; 
     } 
     if (itemPosition % mGridSize == 0) { 
      outRect.left = 0; 
      outRect.right = padding; 
      mNeedLeftSpacing = true; 
     } else if ((itemPosition + 1) % mGridSize == 0) { 
      mNeedLeftSpacing = false; 
      outRect.right = 0; 
      outRect.left = padding; 
     } else if (mNeedLeftSpacing) { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx - padding; 
      if ((itemPosition + 2) % mGridSize == 0) { 
       outRect.right = mSizeGridSpacingPx - padding; 
      } else { 
       outRect.right = mSizeGridSpacingPx/2; 
      } 
     } else if ((itemPosition + 2) % mGridSize == 0) { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx/2; 
      outRect.right = mSizeGridSpacingPx - padding; 
     } else { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx/2; 
      outRect.right = mSizeGridSpacingPx/2; 
     } 
     outRect.bottom = 0; 
    } 
} 

enter image description here enter image description here

+0

Dzięki, Dmitry! Działa jak marzenie! – AlexKorovyansky

+0

@AlexKorovyansky tego samego interfejsu użytkownika, ale z Załaduj więcej przedmiotów, jak można to osiągnąć? – Erum

+0

To jest dobry początek, ale te linie narysowane pod dolnym rzędem, gdy elementy są animowane (to znaczy, notifyItemChanged wywołane) –

5

mogą Masz swoją odpowiedź, ale nadal jestem delegowania moje rozwiązanie, które mogą pomóc innym. Można go wykorzystać do wyświetlania pionowych, poziomych list lub widoków siatki, przekazując orientację.

import android.content.Context; 
import android.content.res.TypedArray; 
import android.graphics.Canvas; 
import android.graphics.Rect; 
import android.graphics.drawable.Drawable; 
import android.support.v7.widget.LinearLayoutManager; 
import android.support.v7.widget.RecyclerView; 
import android.view.View; 

public class DividerItemDecoration extends RecyclerView.ItemDecoration { 

    private static final int[] ATTRS = new int[]{ 
      android.R.attr.listDivider 
    }; 

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 
    public static final int GRID = 2; 

    private Drawable mDivider; 

    private int mOrientation; 

    public DividerItemDecoration(Context context, int orientation) { 
     final TypedArray a = context.obtainStyledAttributes(ATTRS); 
     mDivider = a.getDrawable(0); 
     a.recycle(); 
     setOrientation(orientation); 
    } 

    public void setOrientation(int orientation) { 
     if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST && orientation != GRID) { 
      throw new IllegalArgumentException("invalid orientation"); 
     } 
     mOrientation = orientation; 
    } 

    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 
     if (mOrientation == VERTICAL_LIST) { 
      drawVertical(c, parent); 
     } else if(mOrientation == HORIZONTAL_LIST){ 
      drawHorizontal(c, parent); 
     } else { 
      drawVertical(c, parent); 
      drawHorizontal(c, parent); 
     } 
    } 

    public void drawVertical(Canvas c, RecyclerView parent) { 
     if (parent.getChildCount() == 0) return; 

     final int left = parent.getPaddingLeft(); 
     final int right = parent.getWidth() - parent.getPaddingRight(); 

     final View child = parent.getChildAt(0); 
     if (child.getHeight() == 0) return; 

     final RecyclerView.LayoutParams params = 
       (RecyclerView.LayoutParams) child.getLayoutParams(); 
     int top = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight(); 
     int bottom = top + mDivider.getIntrinsicHeight(); 

     final int parentBottom = parent.getHeight() - parent.getPaddingBottom(); 
     while (bottom < parentBottom) { 
      mDivider.setBounds(left, top, right, bottom); 
      mDivider.draw(c); 

      top += mDivider.getIntrinsicHeight() + params.topMargin + child.getHeight() + params.bottomMargin + mDivider.getIntrinsicHeight(); 
      bottom = top + mDivider.getIntrinsicHeight(); 
     } 
    } 

    public void drawHorizontal(Canvas c, RecyclerView parent) { 
     final int top = parent.getPaddingTop(); 
     final int bottom = parent.getHeight() - parent.getPaddingBottom(); 

     final int childCount = parent.getChildCount(); 
     for (int i = 0; i < childCount; i++) { 
      final View child = parent.getChildAt(i); 
      final RecyclerView.LayoutParams params = 
        (RecyclerView.LayoutParams) child.getLayoutParams(); 
      final int left = child.getRight() + params.rightMargin + mDivider.getIntrinsicHeight(); 
      final int right = left + mDivider.getIntrinsicWidth(); 
      mDivider.setBounds(left, top, right, bottom); 
      mDivider.draw(c); 
     } 
    } 

    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
     if (mOrientation == VERTICAL_LIST) { 
      outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 
     } else if(mOrientation == HORIZONTAL_LIST) { 
      outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 
     } else { 
      outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); 
     } 
    } 
} 
+1

Nie działa dobrze w przypadku, gdy wiersze siatki są różnych rozmiarów –

0
@BindingAdapter({"bind:adapter"}) 
    public static void bind(RecyclerView view, RecyclerView.Adapter<BaseViewHolder> adapter) { 
     view.setLayoutManager(new GridLayoutManager(view.getContext(), 3)); 
     view.addItemDecoration(new SpacesItemDecorationGrid(view.getContext(), 4, 3)); 
     view.setItemAnimator(new DefaultItemAnimator()); 
     view.setAdapter(adapter); 
    } 


public class SpacesItemDecorationGrid extends RecyclerView.ItemDecoration { 

    private int mSizeGridSpacingPx; 
    private int mGridSize; 
    private boolean mNeedLeftSpacing = false; 

    /** 
    * @param gridSpacingPx 
    * @param gridSize 
    */ 
    SpacesItemDecorationGrid(Context context, int gridSpacingPx, int gridSize) { 
     mSizeGridSpacingPx = (int) Util.convertDpToPixel(gridSpacingPx, context); 
     mGridSize = gridSize; 
    } 

    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
     int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1))/mGridSize); 
     int padding = parent.getWidth()/mGridSize - frameWidth; 
     int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition(); 
     int itemCount = parent.getAdapter().getItemCount() - mGridSize; 


    /*  if (itemPosition < mGridSize) { 
      outRect.top = mSizeGridSpacingPx; 
     } else { 
        outRect.top = mSizeGridSpacingPx; 
     }*/ 
     outRect.top = mSizeGridSpacingPx; 
     if (itemPosition % mGridSize == 0) { 
      outRect.left = mSizeGridSpacingPx; 
      outRect.right = padding; 
      mNeedLeftSpacing = true; 
     } else if ((itemPosition + 1) % mGridSize == 0) { 
      mNeedLeftSpacing = false; 
      outRect.right = mSizeGridSpacingPx; 
      outRect.left = padding; 
     } else if (mNeedLeftSpacing) { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx - padding; 
      if ((itemPosition + 2) % mGridSize == 0) { 
       outRect.right = mSizeGridSpacingPx - padding; 
      } else { 
       outRect.right = mSizeGridSpacingPx/2; 
      } 
     } else if ((itemPosition + 2) % mGridSize == 0) { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx/2; 
      outRect.right = mSizeGridSpacingPx - padding; 
     } else { 
      mNeedLeftSpacing = false; 
      outRect.left = mSizeGridSpacingPx/2; 
      outRect.right = mSizeGridSpacingPx/2; 
     } 
     if (itemPosition > itemCount) { 
      outRect.bottom = mSizeGridSpacingPx; 
     } else { 
      outRect.bottom = 0; 
     } 

    } 

} 
+1

Proszę rozważyć dodanie wyjaśnienia, co robi kod. Tylko kody odpowiedzi _may_ technicznie odpowiadają na pytanie, ale są mniej prawdopodobne, że będą przydatne dla innych użytkowników z podobnymi problemami. –

0

Jednym z bardziej proste rozwiązanie, że pracował dla mnie. Mam nadzieję, że może się przydać.

class GridItemDecorator(val context: Context, private val spacingDp: Int, private val mGridSize: Int) : RecyclerView.ItemDecoration() { 

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 

     val resources = context.resources 
     val spacingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingDp.toFloat(), resources.displayMetrics) 

     val bit = if (spacingPx > mGridSize) Math.round(spacingPx/mGridSize) else 1 
     val itemPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition 

     outRect.top = if (itemPosition < mGridSize) 0 else bit * mGridSize 
     outRect.bottom = 0 

     val rowPosition = itemPosition % mGridSize 
     outRect.left = rowPosition * bit 
     outRect.right = (mGridSize - rowPosition - 1) * bit 

    } 
} 
0

Oto prostsze i bardziej przyjazne dla użytkownika realizacja:

public class MediaSpaceDecoration extends RecyclerView.ItemDecoration { 
    private final int spacing; 
    private final List<Integer> allowedViewTypes = Arrays.asList(
      R.layout.item_image, 
      R.layout.item_blur); 

    public MediaSpaceDecoration(int spacing) { 
     this.spacing = spacing; 
    } 

    @Override 
    public void getItemOffsets(Rect outRect, 
           View view, 
           RecyclerView parent, 
           RecyclerView.State state) { 
     final int position = parent.getChildAdapterPosition(view); 
     if (!isMedia(parent, position)) { 
      return; 
     } 

     final int totalSpanCount = getTotalSpanCount(parent); 
     int spanSize = getItemSpanSize(parent, position); 
     if (totalSpanCount == spanSize) { 
      return; 
     } 

     outRect.top = isInTheFirstRow(position, totalSpanCount) ? 0 : spacing; 
     outRect.left = isFirstInRow(position, totalSpanCount) ? 0 : spacing/2; 
     outRect.right = isLastInRow(position, totalSpanCount) ? 0 : spacing/2; 
     outRect.bottom = 0; // don't need 
    } 

    private boolean isInTheFirstRow(int position, int spanCount) { 
     return position < spanCount; 
    } 

    private boolean isFirstInRow(int position, int spanCount) { 
     return position % spanCount == 0; 
    } 

    private boolean isLastInRow(int position, int spanCount) { 
     return isFirstInRow(position + 1, spanCount); 
    } 

    private int getTotalSpanCount(RecyclerView parent) { 
     final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); 
     return layoutManager instanceof GridLayoutManager 
      ? ((GridLayoutManager) layoutManager).getSpanCount() 
      : 1; 
    } 

    private int getItemSpanSize(RecyclerView parent, int position) { 
     final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); 
     return layoutManager instanceof GridLayoutManager 
      ? ((GridLayoutManager) layoutManager).getSpanSizeLookup().getSpanSize(position) 
      : 1; 
    } 

    private boolean isMedia(RecyclerView parent, int viewPosition) { 
     final RecyclerView.Adapter adapter = parent.getAdapter(); 
     final int viewType = adapter.getItemViewType(viewPosition); 
     return allowedViewTypes.contains(viewType); 
    } 
} 

ja również sprawdzić przed ustawieniem outRect bo mam różne spanSize s dla każdego viewType i muszę dodać dodatkowy środkowy-space tylko dla allowedViewTypes. Możesz łatwo usunąć tę weryfikację, a kod będzie jeszcze prostszy. Wygląda to dla mnie: GridLayout with a different spanSize for each viewType

Powiązane problemy