J'essaie d'utiliser RecyclerView
et GridLayoutManager
pour créer une grille à 3 colonnes, et ItemDecoration
pour espacer les colonnes, maintenant le problème est que la largeur de l'élément dans la troisième colonne est plus petite que l'élément dans les première et deuxième colonnes! Voir la capture d'écran ci-dessous.
Si je n'ajoute pas la coutume ItemDecoration
à RecyclerView
, tout va bien.
Voici mon code:
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());
}
}
}
Ce serait très apprécié si quelqu'un pouvait expliquer ce problème.
J'ai trouvé la raison du problème par moi-même. Le décalage qui est fait dans ItemDecoration
est considéré comme faisant partie des dimensions de l'article (largeur et hauteur)!
Jetons un coup d'œil à l'exemple de code et à la capture d'écran de la question ci-dessus. La largeur de la capture d'écran est de 480 pixels. Ici, vous avez 3 colonnes. La largeur de chaque élément est de 480/3 = 160 pixels. Dans SpacingDecoration
, j'ajoute un décalage à gauche (20 pixels) sur les première et deuxième colonnes, de sorte que la largeur du contenu des premier et deuxième éléments de colonne est 160-20 = 140, puis que j'ajoute les décalages gauche et droit sur le troisième élément de colonne, la largeur du contenu de la 3ème colonne est donc 160-20-20 = 120.
Maintenant, nous voulons que le contenu de chaque élément (le rectangle coloré) ait la même largeur, nous devons calculer combien chaque élément de colonne divise l'espacement total d'une ligne, mais je suis pauvre pour écrire une analyse détaillée, alors voici un calcul approximatif processus, vous pouvez le passer et sauter à la conclusion.
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)
On peut conclure :
itemLeftOffset = spacing * ((columnCount - colunmnIndex) / columnCount)
itemRightOffset = spacing * ((colunmnIndex + 1) / columnCount)
colunmnIndex est supérieur à 0 et inférieur à columnCount.
Voici ma coutume ItemDecoration
pour l'espacement, cela fonctionne bien avec LinearLayoutManager
, GridLayoutManager
et StaggeredGridLayoutManager
, tous les éléments ont la même largeur. Vous pouvez l'utiliser directement dans votre code.
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;
}
}
}
}
J'ai écrit un ItemDecoration plus robuste basé sur la réponse de @AvatarQing: SCommonItemDecoration
Vous pouvez définir le même espace vertical ou horizontal entre les éléments, mais vous pouvez également définir un espace différent pour différents types d'éléments.