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.
- Cómo crear un menú contextual para RecyclerView
- Teclado suave se abre y luego se cierra al hacer clic en edittext
- El adaptador de RecyclerView notifyDataSetChanged no funciona
- Uso de la vista de reciclaje con una base de datos
- RecyclerView horizontal con relleno inicial
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.
- Los datos de Firebase no se muestran en RecyclerView
- ¿Hay una manera de compartir un mismo LayoutManager entre múltiples RecyclerViews anidados
- RecyclerView no se actualiza después de NotifyDataSetChanged (), el contenido aparece después de encender y apagar la pantalla
- Deshabilitar RecyclerView itemAnimator para elementos específicos
- Pasar los datos de los elementos de los clics hechos por RecyclerView CardView a la actividad
- NullPointerException en ViewPager con Recyclerview
- Datos de SQLite a un RecyclerView
- Cómo decorar selectivamente los elementos de RecyclerView
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.
- ¿Cuál es la diferencia entre el fragmento y la actividad? ¿Qué código se puede escribir en fragmento?
- ¿Cómo crear una vista de árbol en Android?