2015-05-29 32 views
15

Tôi đang cố gắng sử dụng RecyclerViewGridLayoutManager để tạo lưới 3 cột và tôi sử dụng ItemDecoration để tạo khoảng cách cột, hiện tại vấn đề là mục chiều rộng trong cột thứ ba nhỏ hơn mục trong cột đầu tiên và cột thứ hai! Xem ảnh chụp màn hình bên dưới.Các mục không có cùng chiều rộng khi sử dụng RecyclerView GridLayoutManager để tạo khoảng cách cột theo ItemDecoration

enter image description here

Nếu tôi không thêm các tùy chỉnh ItemDecoration-RecyclerView, mọi thứ đều OK.

Đây là mã của tôi:

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

Nó sẽ được đánh giá cao nếu ai đó có thể giải thích vấn đề này.

Trả lời

9

Tôi đã tìm ra nguyên nhân của sự cố. Giá trị bù đắp được thực hiện trong ItemDecoration được coi là một phần của kích thước của mặt hàng (chiều rộng và chiều cao)!

Hãy xem mã mẫu và chụp màn hình trong câu hỏi ở trên. Chiều rộng của ảnh chụp màn hình là 480 pixel và đây là 3 cột, chiều rộng của mỗi mục là 480/3 = 160 pixel. Trong SpacingDecoration, tôi thêm khoảng trống trái (20 pixel) vào cột đầu tiên và cột thứ hai, vì vậy chiều rộng của nội dung của mục cột đầu tiên và thứ hai là 160-20 = 140, sau đó tôi thêm cả bù trừ trái và phải trên mục cột thứ 3, do đó chiều rộng của nội dung của mục cột thứ 3 là 160-20-20 = 120.

Bây giờ chúng tôi muốn làm cho nội dung của mỗi mục (hình chữ nhật có màu) có chiều rộng giống nhau, chúng ta phải tính toán bao nhiêu mỗi mục cột chia tổng khoảng cách của một hàng, nhưng tôi nghèo để viết phân tích chi tiết. viết một quá trình tính toán thô, bạn có thể vượt qua nó và nhảy đến kết luận.

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) 

Chúng tôi có thể kết luận:

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

colunmnIndex là lớn hơn 0 và nhỏ hơn columnCount.


Đây là tùy chỉnh của tôi ItemDecoration cho khoảng cách, nó hoạt động tốt với LinearLayoutManager, GridLayoutManagerStaggeredGridLayoutManager, tất cả các mục là chiều rộng tương tự. Bạn có thể sử dụng nó trực tiếp trong mã của bạn.

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

Trong công thức của bạn, '+ 1' trong lề phải không thực sự là' + 1', nó phải là '+ spanSize của cell'. (bạn có thể lấy kích thước span với spanSizeLookup hoặc trong LayoutParams của ô) – pdegand59

2

Tôi đã viết một ItemDecoration mạnh mẽ hơn dựa trên @AvatarQing 's câu trả lời: SCommonItemDecoration

Bạn có thể đặt cùng một không gian theo chiều dọc hoặc ngang giữa các mục, ngoài ra bạn có thể thiết lập không gian khác nhau để loại khác nhau của các mặt hàng .

enter image description here

+0

Cảm ơn bạn đã chia sẻ @Bos! Các phần chính của lớp học của bạn hiện được bao gồm và cải thiện cho thư viện https://github.com/davideas/FlexibleAdapter để xử lý các tính năng nâng cao hơn. – Davidea

Các vấn đề liên quan