2015-05-29 17 views
15

Próbuję użyć RecyclerView i GridLayoutManager zrobić siatkę 3 kolumny i używam ItemDecoration aby odstępy między kolumnami, teraz problemem jest pozycja na szerokość w trzeciej kolumnie jest mniejsza niż w pierwszej i drugiej kolumnie! Zobacz zrzut ekranu poniżej.przedmioty nie są tej samej szerokości przy użyciu RecyclerView GridLayoutManager aby odstępy kolumny ItemDecoration

enter image description here

Gdybym nie dodawać zwyczaj ItemDecoration do RecyclerView, wszystko jest OK.

Oto mój kod:

MainActivity.java:

public class MainActivity extends AppCompatActivity { 

    private RecyclerView mRecyclerView; 
    private MyAdapter mAdapter; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 
     mAdapter = new MyAdapter(); 
     mRecyclerView.setAdapter(mAdapter); 

     GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3); 
     mRecyclerView.setLayoutManager(gridLayoutManager); 

     int horizontalSpacing = 20; 
     int verticalSpacing = 10; 
     SpacingDecoration decoration = new SpacingDecoration(horizontalSpacing, verticalSpacing, true); 
     mRecyclerView.addItemDecoration(decoration); 
    } 


    private static class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 

     private int[] mColors = new int[]{Color.RED, Color.BLUE, Color.MAGENTA}; 

     private static class ItemHolder extends RecyclerView.ViewHolder { 

      public MyTextView title; 

      public ItemHolder(View itemView) { 
       super(itemView); 
       title = (MyTextView) itemView.findViewById(android.R.id.text1); 
       title.setTextColor(Color.WHITE); 
      } 
     } 

     @Override 
     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
      View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); 
      ItemHolder holder = new ItemHolder(itemView); 
      holder.itemView.setOnClickListener(itemClickListener); 
      return holder; 
     } 

     @Override 
     public void onBindViewHolder(RecyclerView.ViewHolder rHolder, int position) { 
      ItemHolder holder = (ItemHolder) rHolder; 

      holder.title.setText(String.format("[%d]width:%d", position, holder.itemView.getWidth())); 
      holder.itemView.setBackgroundColor(mColors[position % mColors.length]); 
      holder.itemView.setTag(position); 
      holder.title.setTag(position); 
     } 

     @Override 
     public int getItemCount() { 
      return 50; 
     } 

     private View.OnClickListener itemClickListener = new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       int position = (int) v.getTag(); 
       showText(v.getContext(), String.format("[%d]->width:%d", position, v.getWidth())); 
      } 
     }; 

    } 

    public static class SpacingDecoration extends RecyclerView.ItemDecoration { 

     private int mHorizontalSpacing = 5; 
     private int mVerticalSpacing = 5; 
     private boolean isSetMargin = true; 

     public SpacingDecoration(int hSpacing, int vSpacing, boolean setMargin) { 
      isSetMargin = setMargin; 
      mHorizontalSpacing = hSpacing; 
      mVerticalSpacing = vSpacing; 
     } 

     @Override 
     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
      boolean isSetMarginLeftAndRight = this.isSetMargin; 
      int bottomOffset = mVerticalSpacing; 
      int leftOffset = 0; 
      int rightOffset = 0; 

      RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams(); 
      if (parent.getLayoutManager() instanceof GridLayoutManager) { 
       GridLayoutManager lm = (GridLayoutManager) parent.getLayoutManager(); 
       GridLayoutManager.LayoutParams gridLp = (GridLayoutManager.LayoutParams) lp; 

       if (gridLp.getSpanSize() == lm.getSpanCount()) { 
        // Current item is occupied the whole row 
        // We just need to care about margin left and right now 
        if (isSetMarginLeftAndRight) { 
         leftOffset = mHorizontalSpacing; 
         rightOffset = mHorizontalSpacing; 
        } 
       } else { 
        // Current item isn't occupied the whole row 
        if (gridLp.getSpanIndex() > 0) { 
         // Set space between items in one row 
         leftOffset = mHorizontalSpacing; 
        } else if (gridLp.getSpanIndex() == 0 && isSetMarginLeftAndRight) { 
         // Set left margin of a row 
         leftOffset = mHorizontalSpacing; 
        } 
        if (gridLp.getSpanIndex() == lm.getSpanCount() - gridLp.getSpanSize() && isSetMarginLeftAndRight) { 
         // Set right margin of a row 
         rightOffset = mHorizontalSpacing; 
        } 
       } 
      } 
      outRect.set(leftOffset, 0, rightOffset, bottomOffset); 
     } 
    } 


    private static Toast sToast; 

    public static void showText(Context context, String text) { 
     if (sToast != null) { 
      sToast.cancel(); 
     } 
     sToast = Toast.makeText(context, text, Toast.LENGTH_LONG); 
     sToast.show(); 
    } 
} 

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent"> 

    <android.support.v7.widget.RecyclerView 
     android:id="@+id/recycler_view" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent"/> 

</RelativeLayout> 

item.xml

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent" 
       android:orientation="vertical"> 

    <com.example.liuqing.rvgldemo.MyTextView 
     android:id="@android:id/text1" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:padding="5dp" 
     android:textColor="#ffffff" 
     android:textAppearance="?android:attr/textAppearanceMedium"/> 
</LinearLayout> 

MyTextView.java

public class MyTextView extends TextView { 

    public MyTextView(Context context) { 
     super(context); 
    } 

    public MyTextView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { 
     super(context, attrs, defStyleAttr); 
    } 

    @Override 
    public void onWindowFocusChanged(boolean hasWindowFocus) { 
     super.onWindowFocusChanged(hasWindowFocus); 
     if (hasWindowFocus) { 
      setText("[" + getTag() + "]width:" + getWidth()); 
     } 
    } 
} 

Będzie to bardzo ważne, jeśli ktoś może wyjaśnić ten problem.

Odpowiedz

9

Znalazłem przyczynę problemu samodzielnie. Przesunięcie wykonane w ItemDecoration jest traktowane jako część wymiarów elementu (szerokość i wysokość)!

Rzućmy okiem na przykładowy kod i zrzut ekranu w powyższym pytaniu. Szerokość przechwytywania ekranu wynosi 480 pikseli, a tutaj są 3 kolumny, a szerokość każdego elementu wynosi 480/3 = 160 pikseli. W SpacingDecoration dodaję lewe przesunięcie (20 pikseli) na pierwszej i drugiej kolumnie, więc szerokość zawartości pierwszej i drugiej kolumny wynosi 160-20 = 140, a następnie dodaję lewe i prawe przesunięcie na elemencie trzeciej kolumny, więc szerokość zawartości trzeciej kolumny kolumny wynosi 160-20-20 = 120.

Teraz chcemy, aby zawartość każdego elementu (kolorowy prostokąt) miała tę samą szerokość, musimy obliczyć, ile każdego elementu kolumny dzieli całkowity odstęp jednego wiersza, ale jestem biedny, aby napisać szczegółową analizę, więc tutaj napisz szorstki proces obliczeniowy, możesz go przekazać i przejść do wniosku.

spacing = 20 
columnCount = 3 
rowWidth = 480 
itemWidth = rowWidth/columnCount 
itemOccupiedSpacing = (spacing * (columnCount + 1))/columnCount = spacing + spacing * (1/columnCount) 
itemContentWidth = itemWidth - itemOccupiedSpacing 

firstItemLeftOffset = spacing = spacing * (3/columnCount) 
firstItemRightOffset = itemOccupiedSpacing - spacing = spacing * (1/columnCount) 
secondItemLeftOffset = spacing - firstRightOffset = spacing * (2/columnCount) 
secondItemRightOffset = itemOccupiedSpacing - secondLeftOffset = spacing * (2/columnCount) 
thirdItemLeftOffset = itemOccupiedSpacing - secondLeftOffset = spacing * (1/columnCount) 
thirdItemRightOffset = spacing = spacing * (3/columnCount) 

możemy stwierdzić:

itemLeftOffset = spacing * ((columnCount - colunmnIndex)/columnCount) 
itemRightOffset = spacing * ((colunmnIndex + 1)/columnCount) 

colunmnIndex jest większe niż 0 i mniej niż columnCount.


Oto mój zwyczaj ItemDecoration dla odstępu, to działa dobrze z LinearLayoutManager, GridLayoutManager i StaggeredGridLayoutManager, wszystkie elementy są tej samej szerokości. Możesz go użyć bezpośrednio w swoim kodzie.

public class SpacingDecoration extends ItemDecoration { 

    private int mHorizontalSpacing = 0; 
    private int mVerticalSpacing = 0; 
    private boolean mIncludeEdge = false; 

    public SpacingDecoration(int hSpacing, int vSpacing, boolean includeEdge) { 
     mHorizontalSpacing = hSpacing; 
     mVerticalSpacing = vSpacing; 
     mIncludeEdge = includeEdge; 
    } 

    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
     super.getItemOffsets(outRect, view, parent, state); 
     // Only handle the vertical situation 
     int position = parent.getChildAdapterPosition(view); 
     if (parent.getLayoutManager() instanceof GridLayoutManager) { 
      GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); 
      int spanCount = layoutManager.getSpanCount(); 
      int column = position % spanCount; 
      getGridItemOffsets(outRect, position, column, spanCount); 
     } else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) { 
      StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) parent.getLayoutManager(); 
      int spanCount = layoutManager.getSpanCount(); 
      StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); 
      int column = lp.getSpanIndex(); 
      getGridItemOffsets(outRect, position, column, spanCount); 
     } else if (parent.getLayoutManager() instanceof LinearLayoutManager) { 
      outRect.left = mHorizontalSpacing; 
      outRect.right = mHorizontalSpacing; 
      if (mIncludeEdge) { 
       if (position == 0) { 
        outRect.top = mVerticalSpacing; 
       } 
       outRect.bottom = mVerticalSpacing; 
      } else { 
       if (position > 0) { 
        outRect.top = mVerticalSpacing; 
       } 
      } 
     } 
    } 

    private void getGridItemOffsets(Rect outRect, int position, int column, int spanCount) { 
     if (mIncludeEdge) { 
      outRect.left = mHorizontalSpacing * (spanCount - column)/spanCount; 
      outRect.right = mHorizontalSpacing * (column + 1)/spanCount; 
      if (position < spanCount) { 
       outRect.top = mVerticalSpacing; 
      } 
      outRect.bottom = mVerticalSpacing; 
     } else { 
      outRect.left = mHorizontalSpacing * column/spanCount; 
      outRect.right = mHorizontalSpacing * (spanCount - 1 - column)/spanCount; 
      if (position >= spanCount) { 
       outRect.top = mVerticalSpacing; 
      } 
     } 
    } 
} 
+1

W swojej formule '+ 1 'w prawym marginesie rzeczywistości nie jest' + 1', powinno być '+ spanSize z cell'. (możesz pobrać rozmiar span za pomocą spanSizeLookup lub w LayoutParams komórki) – pdegand59

2

ja napisałem bardziej solidnego ItemDecoration podstawie @AvatarQing „s odpowiedź: SCommonItemDecoration

Można ustawić taką samą pionową lub poziomą przestrzeń pomiędzy elementami, oprócz można ustawić inną przestrzeń do różnego rodzaju przedmiotów .

enter image description here

+0

Dzięki za udostępnianie @ Bos! Kluczowe części klasy są teraz dołączone i ulepszone w bibliotece https://github.com/davideas/FlexibleAdapter, aby obsługiwać bardziej zaawansowane funkcje. – Davidea

Powiązane problemy