recyclerview manager gridlayout example columns java android android-recyclerview gridlayoutmanager

java - example - Los elementos no tienen el mismo ancho cuando se utiliza RecyclerView GridLayoutManager para hacer el espaciado de columnas por ItemDecoration



recyclerview grid manager (2)

Encontré la razón del problema por mí mismo. El desplazamiento realizado en ItemDecoration se considera como parte de las dimensiones del elemento (ancho y alto).

Veamos el código de muestra y la captura de pantalla en la pregunta anterior. El ancho de la captura de pantalla es de 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 y segunda columnas, por lo que el ancho del contenido del primer y segundo elemento de la columna es 160-20 = 140, luego agrego el desplazamiento a la izquierda y al derecho en el tercer elemento de la columna, por lo que el ancho del contenido del elemento de la tercera columna es 160-20-20 = 120.

Ahora queremos que el contenido de cada elemento (el rectángulo coloreado) tenga el mismo ancho, debemos calcular cuánto divide cada elemento de columna el espaciado total de una fila, pero soy pobre para escribir un análisis detallado, por lo que aquí escribo un cálculo aproximado proceso, puede pasarlo 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, funciona bien con LinearLayoutManager , GridLayoutManager y StaggeredGridLayoutManager , todos los elementos tienen el mismo ancho. Puedes usarlo directamente en tu 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; } } } }

Estoy tratando de usar RecyclerView y GridLayoutManager para hacer una cuadrícula de 3 columnas, y utilizo ItemDecoration para hacer el espaciado de columnas, ¡ahora el problema es que el ancho del elemento en la tercera columna es más pequeño que el elemento en la primera y segunda columna! Ver la captura de pantalla a continuación.

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

Será muy apreciado si alguien puede explicar este problema.


Escribí una ItemDecoration más robusta basada en la respuesta de SCommonItemDecoration : SCommonItemDecoration

Puede configurar el mismo espacio vertical u horizontal entre los elementos, además de configurar diferentes espacios para diferentes tipos de elementos.