Los elementos no tienen el mismo ancho cuando se utiliza RecyclerView GridLayoutManager para establecer el espaciado de columnas por ItemDecoration

Estoy intentando utilizar RecyclerView y GridLayoutManager para hacer una rejilla de 3 columnas, y yo uso ItemDecoration para hacer espaciado de columna, ahora el problema es el ancho del artículo en la tercera columna es más pequeño que el artículo en la primera y segunda columna! Vea la captura de pantalla abajo.

Introduzca aquí la descripción de la imagen

Si no agrego el ItemDecoration personalizado a RecyclerView , todo está bien.

Aquí está mi código:

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

Se apreciará mucho si alguien puede explicar este problema.

Encontré la razón del problema por mí mismo. El offset que se hizo en ItemDecoration se considera como una parte de las dimensiones del artículo (ancho y alto)!

Echemos un vistazo al código de ejemplo y captura de pantalla en la pregunta anterior. El ancho de la captura de pantalla es 480 píxeles, y aquí hay 3 columnas, el ancho de cada elemento es 480/3 = 160 píxeles. En SpacingDecoration , agrego un desplazamiento a la izquierda (20 píxeles) en la primera columna y la segunda, por lo que el ancho del contenido del primer y segundo elemento de columna es 160-20 = 140, a continuación, agrego a la izquierda y la derecha en el elemento de la columna 3, De modo que el ancho del contenido del elemento de la 3ª columna es 160-20-20 = 120.

Ahora queremos hacer que el contenido de cada elemento (el rectángulo coloreado) tenga el mismo ancho, debemos calcular cuánto cada elemento de columna divide el espaciado total de una fila, pero soy pobre para escribir un análisis detallado, así que aquí escribo un cálculo aproximado Proceso, se puede pasar y saltar a la conclusió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) 

Podemos concluir :

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

ColunmnIndex es mayor que 0 y menor que columnCount.


Aquí está mi ItemDecoration personalizado para el espaciado, que funciona bien con LinearLayoutManager , GridLayoutManager y StaggeredGridLayoutManager , todos los elementos son el mismo ancho. Puede usarlo directamente en su código.

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

He escrito una ItemDecoration más robusta basada en la respuesta de @AvatarQing : SCommonItemDecoration

Puede establecer el mismo espacio vertical u horizontal entre los elementos, además puede establecer un espacio diferente para diferentes tipos de elementos.

Introduzca aquí la descripción de la imagen

  • ¿Cuál es la mejora de RecyclerView sobre ListView?
  • RecyclerView Adapter da error "La jerarquía del tipo RecycleAdapter es inconsistente"
  • ¿Cómo añadir una vista de pie de página en RECYCLER VIEW?
  • ¿Cómo animar recyclerview en desplazamiento como Google plus / Google newsstand?
  • El más correctamente para guardar el estado de RecyclerView?
  • Firebase RecyclerView - Desplácese sobre la animación cuando se añade un elemento nuevo
  • Recyclerview en un fragmento no muestra nada
  • Extraño comportamiento de las imágenes en RecyclerView
  • Práctica recomendada para eliminar asincrónicamente un elemento ListView / RecyclerView
  • Implementar el gesto de deslizamiento en un elemento de RecyclerView?
  • Facebook NativeAdsManager saliendo de la excepción de memoria en RecyclerView
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.