Как добавить разделители и пробелы между элементами в RecyclerView?
Это пример того, как это можно было сделать ранее в ListView
класс, используя параметры divider и dividerHeight:
<ListView
android:id="@+id/activity_home_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent"
android:dividerHeight="8dp"/>
Однако я не вижу такой возможности в RecyclerView
учебный класс.
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_home_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
В этом случае можно ли определять поля и / или добавлять настраиваемое представление делителя непосредственно в макет элемента списка или есть лучший способ достичь моей цели?
46 ответов
Я добавил строку в элемент списка, как показано ниже
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/dividerColor"/>
1px нарисует тонкую линию.
Если вы хотите скрыть разделитель для последнего ряда, то
divider.setVisiblity(View.GONE);
на onBindViewHolder для последнего элемента списка.
1. Одним из путей является совместное использование карт и вида переработчика, мы можем легко добавить эффект, например, делитель. ех. https://developer.android.com/training/material/lists-cards.html
2. и другое, добавив представление в качестве разделителя к списку_символов представления переработчика.
<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorAccent" />
RecyclerView
немного отличается от ListView
, На самом деле, RecyclerView
нужен ListView
как структура в нем. Например, LinearLayout
, LinearLayout
имеет параметры для разделения каждого элемента. В приведенном ниже коде у меня есть RecyclerView
состоящий из CardView
объекты внутри LinearLayout
с "отступом", который поместит некоторое пространство между элементами. Сделайте это пространство действительно маленьким, и вы получите линию.
Вот представление Recycler в recyclerview_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".ToDoList">
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/todo_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
И вот как выглядит каждый элемент (и он выглядит как разделенный из-за андроида: отступ в LinearLayout, который окружает все.) В другом файле: cards_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
**android:padding="@dimen/activity_vertical_margin"**>
<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="100dp"
android:elevation="30dp"
card_view:cardElevation="3dp">
<TextView
android:id="@+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v7.widget.CardView>
</LinearLayout>
Действительно простое решение - использовать https://github.com/yqritc/RecyclerView-FlexibleDivider.
Добавить зависимость:
compile 'com.yqritc:recyclerview-flexibledivider:1.4.0'
Добавьте в свой список переработчиков:
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(context).build());
И вы сделали!
Реализация собственной версии RecyclerView.ItemDecoration
public class SpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spacingPx;
private boolean addStartSpacing;
private boolean addEndSpacing;
public SpacingItemDecoration(int spacingPx) {
this(spacingPx, false, false);
}
public SpacingItemDecoration(int spacingPx, boolean addStartSpacing, boolean addEndSpacing) {
this.spacingPx = spacingPx;
this.addStartSpacing = addStartSpacing;
this.addEndSpacing = addEndSpacing;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (spacingPx <= 0) {
return;
}
if (addStartSpacing && parent.getChildLayoutPosition(view) < 1 || parent.getChildLayoutPosition(view) >= 1) {
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = spacingPx;
} else {
outRect.left = spacingPx;
}
}
if (addEndSpacing && parent.getChildAdapterPosition(view) == getTotalItemCount(parent) - 1) {
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.bottom = spacingPx;
} else {
outRect.right = spacingPx;
}
}
}
private int getTotalItemCount(RecyclerView parent) {
return parent.getAdapter().getItemCount();
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
return ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
} else {
throw new IllegalStateException("SpacingItemDecoration can only be used with a LinearLayoutManager.");
}
}
}
Используя PrimeAdapter, обработка разделителей в RecyclerView
с может быть так просто. Он предоставляет множество функций и большую гибкость для создания и управления разделителями.
Вы можете создать адаптер следующим образом: (Пожалуйста, смотрите полный документ об использовании в github)
val adapter = PrimeAdapter.with(recyclerView)
.setLayoutManager(LinearLayoutManager(activity))
.set()
.build(ActorAdapter::class.java)
После создания экземпляра адаптера вы можете просто добавить разделитель, просто используя:
//----- default divider:
adapter.setDivider()
//----- divider with custom drawable:
adapter.setDivider(ContextCompat.getDrawable(context, R.drawable.divider))
//----- divider with custom color:
adapter.setDivider(Color.RED)
//----- divider with custom color and custom inset:
adapter.setDivider(Color.RED, insetLeft = 16, insetRight = 16)
//----- deactivate dividers:
adapter.setDivider(null)
RecyclerView не предоставляет простой интерфейс для рисования разделителей списков. Но на самом деле это дает нам гораздо более гибкий способ рисования разделителей. Мы используем RecyclerView.ItemDecoration, чтобы украсить плитки RecyclerView разделителями или чем угодно. Именно поэтому он называется ItemDecoration.
Как описано в Руководстве по проектированию материалов:
Разделитель находится в пределах базовой линии плитки.
Поэтому, если вы хотите следовать Руководству по проектированию материалов, вам не нужно дополнительное пространство для рисования разделителей. Просто нарисуйте их на плитке. Тем не менее, вы имеете право делать все, что хотите. Поэтому я реализовал один, который дает вам возможность устанавливать вставки, а также рисовать на / под плитками.
public class InsetDivider extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Paint mPaint;
// in pixel
private int mDividerHeight;
// left inset for vertical list, top inset for horizontal list
private int mFirstInset;
// right inset for vertical list, bottom inset for horizontal list
private int mSecondInset;
private int mColor;
private int mOrientation;
// set it to true to draw divider on the tile, or false to draw beside the tile.
// if you set it to false and have inset at the same time, you may see the background of
// the parent of RecyclerView.
private boolean mOverlay;
private InsetDivider() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
}
public int getDividerHeight() {
return mDividerHeight;
}
public void setDividerHeight(int dividerHeight) {
this.mDividerHeight = dividerHeight;
}
public int getFirstInset() {
return mFirstInset;
}
public void setFirstInset(int firstInset) {
this.mFirstInset = firstInset;
}
public int getSecondInset() {
return mSecondInset;
}
public void setSecondInset(int secondInset) {
this.mSecondInset = secondInset;
}
public int getColor() {
return mColor;
}
public void setColor(int color) {
this.mColor = color;
mPaint.setColor(color);
}
public int getOrientation() {
return mOrientation;
}
public void setOrientation(int orientation) {
this.mOrientation = orientation;
}
public boolean getOverlay() {
return mOverlay;
}
public void setOverlay(boolean overlay) {
this.mOverlay = overlay;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
protected void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft() + mFirstInset;
final int right = parent.getWidth() - parent.getPaddingRight() - mSecondInset;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
if (parent.getChildAdapterPosition(child) == (parent.getAdapter().getItemCount() - 1)) {
continue;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int bottom;
final int top;
if (mOverlay) {
bottom = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
top = bottom - mDividerHeight;
} else {
top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
bottom = top + mDividerHeight;
}
c.drawRect(left, top, right, bottom, mPaint);
}
}
protected void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop() + mFirstInset;
final int bottom = parent.getHeight() - parent.getPaddingBottom() - mSecondInset;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
if (parent.getChildAdapterPosition(child) == (parent.getAdapter().getItemCount() - 1)) {
continue;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int right;
final int left;
if (mOverlay) {
right = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
left = right - mDividerHeight;
} else {
left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
right = left + mDividerHeight;
}
c.drawRect(left, top, right, bottom, mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOverlay) {
super.getItemOffsets(outRect, view, parent, state);
return;
}
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDividerHeight);
} else {
outRect.set(0, 0, mDividerHeight, 0);
}
}
/**
* Handy builder for creating {@link InsetDivider} instance.
*/
public static class Builder {
private Context mContext;
private int mDividerHeight;
private int mFirstInset;
private int mSecondInset;
private int mColor;
private int mOrientation;
private boolean mOverlay = true; // set default to true to follow Material Design Guidelines
public Builder(Context context) {
mContext = context;
}
public Builder dividerHeight(int dividerHeight) {
mDividerHeight = dividerHeight;
return this;
}
public Builder insets(int firstInset, int secondInset) {
mFirstInset = firstInset;
mSecondInset = secondInset;
return this;
}
public Builder color(@ColorInt int color) {
mColor = color;
return this;
}
public Builder orientation(int orientation) {
mOrientation = orientation;
return this;
}
public Builder overlay(boolean overlay) {
mOverlay = overlay;
return this;
}
public InsetDivider build() {
InsetDivider insetDivider = new InsetDivider();
if (mDividerHeight == 0) {
// Set default divider height to 1dp.
insetDivider.setDividerHeight(mContext.getResources().getDimensionPixelSize(R.dimen.divider_height));
} else if (mDividerHeight > 0) {
insetDivider.setDividerHeight(mDividerHeight);
} else {
throw new IllegalArgumentException("Divider's height can't be negative.");
}
insetDivider.setFirstInset(mFirstInset < 0 ? 0 : mFirstInset);
insetDivider.setSecondInset(mSecondInset < 0 ? 0 : mSecondInset);
if (mColor == 0) {
throw new IllegalArgumentException("Don't forget to set color");
} else {
insetDivider.setColor(mColor);
}
if (mOrientation != InsetDivider.HORIZONTAL_LIST && mOrientation != InsetDivider.VERTICAL_LIST) {
throw new IllegalArgumentException("Invalid orientation");
} else {
insetDivider.setOrientation(mOrientation);
}
insetDivider.setOverlay(mOverlay);
return insetDivider;
}
}
}
И вы можете использовать это так:
ItemDecoration divider = new InsetDivider.Builder(this)
.orientation(InsetDivider.VERTICAL_LIST)
.dividerHeight(getResources().getDimensionPixelSize(R.dimen.divider_height))
.color(getResources().getColor(R.color.colorAccent))
.insets(getResources().getDimensionPixelSize(R.dimen.divider_inset), 0)
.overlay(true)
.build();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(divider);
Я также написал демонстрационное приложение о том, как реализовать ItemDecoration и как их использовать. Вы можете проверить мой GitHub репо https://github.com/gejiaheng/Dividers-For-RecyclerView. В нем есть три реализации:
- UnderneathDivider
- OverlayDivider
- InsetDivider
Использование RecyclerView с ItemDecorations — пример базового разделителя в Kotlin android
Полный образец, включая конструктор, возможность добавления полей или использования цвета ресурса.
class SeparatorDecoration constructor(ctx: Context, @ColorRes dividerColor: Int, heightDp: Float) :
RecyclerView.ItemDecoration() {
private val mPaints = Paint()
init {
mPaints.color = dividerColor
val thickness = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
heightDp,
ctx.resources.displayMetrics
)
mPaints.strokeWidth = thickness
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val params = view.layoutParams as RecyclerView.LayoutParams
// and add a separator to any view but the last one
val p = params.viewAdapterPosition
// and add a separator to any view but the last one
if (p < state.itemCount) outRect[0, 0, 0] =
mPaints.strokeWidth.toInt() // left, top, right, bottom
else outRect.setEmpty() // 0, 0, 0, 0
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
// we set the stroke width before, so as to correctly draw the line we have to offset by width / 2
val offset = (mPaints.strokeWidth / 2).roundToInt()
// this will iterate over every visible view
for (i in 0 until parent.childCount) {
// get the view
val view: View = parent.getChildAt(i)
val params = view.layoutParams as RecyclerView.LayoutParams
// get the position
val position = params.viewAdapterPosition
// and finally draw the separator
if (position < state.itemCount) {
c.drawLine(
view.left.toFloat(),
(view.bottom + offset).toFloat(),
view.right.toFloat(), (view.bottom + offset).toFloat(), mPaints
)
}
}
}
}
Настройка проста. Просто добавьте свои украшения вместе с остальной частью начальной настройки вашего recyclerView.
val decoration = SeparatorDecoration(context, R.color.primaryColor, 1.5f)
recyclerview.addItemDecoration(decoration)
public class VerticalItemDecoration extends RecyclerView.ItemDecoration {
private boolean verticalOrientation = true;
private int space = 10;
public VerticalItemDecoration(int value, boolean verticalOrientation) {
this.space = value;
this.verticalOrientation = verticalOrientation;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
//skip first item in the list
if (parent.getChildAdapterPosition(view) != 0) {
if (verticalOrientation) {
outRect.set(space, 0, 0, 0);
} else if (!verticalOrientation) {
outRect.set(0, space, 0, 0);
}
}
}
}
mCompletedShippingRecyclerView.addItemDecoration(new VerticalItemDecoration(20,false));
У меня есть очень простой способ добавить разделитель в RecyclerView. Используйте пользовательский адаптер для изменения макета представления переработчика, а затем вместе с элементами представления переработчика добавьте LinearLayout с цветом фона (который будет цветом разделителя) и добавьте высоту 1dp (или согласно вашему требованию) и ширину, чтобы соответствовать родительскому элементу.,
Вот пример кода.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="18dp">
<TextView
android:id="@+id/list_row_SNO"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".8"
android:layout_gravity="end"
android:text="44."
android:textAlignment="center"
android:textSize="24sp"
android:textColor="@color/colorBlack"
android:fontFamily="sans-serif-condensed" />
<TextView
android:id="@+id/list_row_Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight=".2"
android:layout_gravity="start"
android:text="Student's application for leave and this what"
android:textAlignment="textStart"
android:textSize="24sp"
android:textColor="@color/colorBlack"
android:fontFamily="sans-serif-condensed" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorHighlight">
</LinearLayout>
Используйте этот класс, чтобы установить разделитель в вашем RecyclerView
,
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing; // item top
}
}
}
}
Если кто-то хочет установить разные значения для spaceBetween, paddingLeft, paddingTop, paddingRight и paddingBottom.
class ItemPaddingDecoration(
private val spaceBetween: Int,
private val paddingLeft: Int = spaceBetween,
private val paddingTop: Int = spaceBetween,
private val paddingRight: Int = spaceBetween,
private val paddingBottom: Int = spaceBetween
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
val orientation = when (val layoutManager = parent.layoutManager) {
is LinearLayoutManager -> {
layoutManager.orientation
}
is GridLayoutManager -> {
layoutManager.orientation
}
else -> {
RecyclerView.HORIZONTAL
}
}
if (orientation == RecyclerView.HORIZONTAL) {
when {
position == 0 -> {
outRect.set(paddingLeft, paddingTop, spaceBetween, paddingBottom)
}
position < parent.adapter!!.itemCount - 1 -> {
outRect.set(0, paddingTop, spaceBetween, paddingBottom)
}
else -> {
outRect.set(0, paddingTop, paddingRight, paddingBottom)
}
}
} else {
when {
position == 0 -> {
outRect.set(paddingLeft, paddingTop, paddingRight, paddingBottom)
}
position < parent.adapter!!.itemCount - 1 -> {
outRect.set(paddingLeft, 0, paddingRight, spaceBetween)
}
else -> {
outRect.set(paddingLeft, 0, paddingRight, paddingBottom)
}
}
}
}
}
Вот мой ленивый подход, но он работает: оберните CardView в макет и установите отступ / поле для родительского макета, чтобы имитировать делитель, и заставьте нормальный делитель обнулять
list_item.xml
<LinearLayout
android:id="@+id/entry_item_layout_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="<divider_size>" > // this is the divider
<CardView
android:layout_width="<width_size>"
android:layout_height="<height_size>">
...
</CardView>
</LinearLayout
list.xml
<RecyclerView
android:divider="@null"
android:layout_width="<width_size>"
android:layout_height="<height_size>"
...
/>
Вот как я бы сделал это в Kotlin для отображения столбцов (сетки)
class ItemDecorationColumns(private val space: Int) : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildLayoutPosition(view)
val manager = parent.layoutManager as GridLayoutManager?
val spanCount = manager?.spanCount ?: 0
if (position < spanCount) {
outRect.top = space
}
if (position % 2 != 0) {
outRect.right = space
}
outRect.left = space
outRect.bottom = space
}
}
Применение
rvAlbums.addItemDecoration(ItemDecorationColumns(resources.getDimensionPixelSize(R.dimen.space)))
Просто добавьте эту строку после инициализации объекта просмотра переработчика.
во фрагменте:
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),android.R.drawable.divider_horizontal_bright));
в деятельности
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,android.R.drawable.divider_horizontal_bright));
Украшение предмета делителя
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int VERTICAL_LIST = 0;
public DividerItemDecoration(ListActivity listActivity, Object p1) {
}
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
mDivider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
mDivider = ContextCompat.getDrawable(context, resId);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
Попробуйте это: https://gist.github.com/shawnlinboy/3a1d8fd28fa690f22d3a35e7749ae010
Реализация ItemDecoration, которая предоставляет функциональность, которая добавляет дополнительное пустое пространство между элементами списка.