RecyclerView развернуть / свернуть элементы
Я хочу развернуть / свернуть элементы моего recyclerView, чтобы показать больше информации. Я хочу добиться того же эффекта от SlideExpandableListView.
По сути, в моем viewHolder у меня есть вид, который не виден, и я хочу сделать плавную анимацию развертывания / сворачивания, а не устанавливать видимость только в VISIBLE/GONE. Мне нужен только элемент, который может быть расширен за один раз, и было бы здорово иметь некоторую высоту, чтобы показать, что элемент выбран.
Это тот же эффект, что и в новом списке истории недавних звонков Android. Параметры "ОБРАТНАЯ СВЯЗЬ" и "ДЕТАЛИ" видны только при выборе элемента.
14 ответов
Пожалуйста, не используйте никакую библиотеку для этого эффекта, вместо этого используйте рекомендуемый способ сделать это в соответствии с Google I/O 2016. В вашем методе recyclerView onBindViewHolder сделайте это:
final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1:position;
TransitionManager.beginDelayedTransition(recyclerView);
notifyDataSetChanged();
}
});
- Где детали это мой вид, который будет отображаться на ощупь (детали звонка в вашем случае. По умолчанию Visibility.GONE).
- mExpandedPosition - это переменная типа int, инициализированная -1
А для крутых эффектов, которые вы хотели, используйте их в качестве атрибутов list_item:
android:background="@drawable/comment_background"
android:stateListAnimator="@animator/comment_selection"
где comment_background:
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true"
android:enterFadeDuration="@android:integer/config_shortAnimTime"
android:exitFadeDuration="@android:integer/config_shortAnimTime">
<item android:state_activated="true" android:drawable="@color/selected_comment_background" />
<item android:drawable="@color/comment_background" />
</selector>
и comment_selection - это:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true">
<objectAnimator
android:propertyName="translationZ"
android:valueTo="@dimen/z_card"
android:duration="2000"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>
<item>
<objectAnimator
android:propertyName="translationZ"
android:valueTo="0dp"
android:duration="2000"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>
</selector>
Для этого просто нужны простые линии, не сложные
в вашем методе onBindViewHolder добавьте ниже код
final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1:position;
notifyItemChanged(position);
}
});
mExpandedPosition - это глобальная переменная int, инициализированная -1
Для тех, кто хочет, чтобы только один предмет был расширен, а другие развалились. Использовать этот
сначала объявите глобальную переменную с previousExpandedPosition = -1
затем
final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
if (isExpanded)
previousExpandedPosition = position;
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1:position;
notifyItemChanged(previousExpandedPosition);
notifyItemChanged(position);
}
});
Готово!!!. Просто и скромно..:)
Не сказать, что это лучший подход, но, похоже, он мне подходит.
Полный код можно найти по адресу: Пример кода по адресу: https://github.com/dbleicher/recyclerview-grid-quickreturn
Прежде всего, добавьте расширенную область в макет ячейки / элемента и сделайте компоновку вмещающей ячейки animateLayoutChanges="true". Это обеспечит анимацию развертывания / свертывания:
<LinearLayout
android:id="@+id/llCardBack"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:animateLayoutChanges="true"
android:padding="4dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|fill_horizontal"
android:padding="10dp"
android:gravity="center"
android:background="@android:color/holo_green_dark"
android:text="This is a long title to show wrapping of text in the view."
android:textColor="@android:color/white"
android:textSize="16sp" />
<TextView
android:id="@+id/tvSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|fill_horizontal"
android:background="@android:color/holo_purple"
android:padding="6dp"
android:text="My subtitle..."
android:textColor="@android:color/white"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/llExpandArea"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:text="Item One" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:text="Item Two" />
</LinearLayout>
</LinearLayout>
Затем сделайте так, чтобы ваш класс адаптера RV реализовал View.OnClickListener, чтобы вы могли воздействовать на элемент, по которому щелкали. Добавьте поле int для хранения позиции одного расширенного представления и инициализируйте его отрицательным значением:
private int expandedPosition = -1;
Наконец, реализуйте методы ViewHolder, onBindViewHolder() и переопределите метод onClick(). Вы развернете представление в onBindViewHolder, если его позиция равна "extendedPosition", и скроете его, если нет. Вы устанавливаете значение extendedPosition в слушателе onClick:
@Override
public void onBindViewHolder(RVAdapter.ViewHolder holder, int position) {
int colorIndex = randy.nextInt(bgColors.length);
holder.tvTitle.setText(mDataset.get(position));
holder.tvTitle.setBackgroundColor(bgColors[colorIndex]);
holder.tvSubTitle.setBackgroundColor(sbgColors[colorIndex]);
if (position == expandedPosition) {
holder.llExpandArea.setVisibility(View.VISIBLE);
} else {
holder.llExpandArea.setVisibility(View.GONE);
}
}
@Override
public void onClick(View view) {
ViewHolder holder = (ViewHolder) view.getTag();
String theString = mDataset.get(holder.getPosition());
// Check for an expanded view, collapse if you find one
if (expandedPosition >= 0) {
int prev = expandedPosition;
notifyItemChanged(prev);
}
// Set the current position to "expanded"
expandedPosition = holder.getPosition();
notifyItemChanged(expandedPosition);
Toast.makeText(mContext, "Clicked: "+theString, Toast.LENGTH_SHORT).show();
}
/**
* Create a ViewHolder to represent your cell layout
* and data element structure
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvTitle;
TextView tvSubTitle;
LinearLayout llExpandArea;
public ViewHolder(View itemView) {
super(itemView);
tvTitle = (TextView) itemView.findViewById(R.id.tvTitle);
tvSubTitle = (TextView) itemView.findViewById(R.id.tvSubTitle);
llExpandArea = (LinearLayout) itemView.findViewById(R.id.llExpandArea);
}
}
Это должно расширять только один элемент за раз, используя системную анимацию по умолчанию для изменения макета. По крайней мере, это работает для меня. Надеюсь, поможет.
Существует очень простая в использовании библиотека с поддержкой gradle: https://github.com/cachapa/ExpandableLayout.
Прямо из библиотеки документов:
<net.cachapa.expandablelayout.ExpandableLinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:el_duration="1000"
app:el_expanded="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click here to toggle expansion" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Fixed height"
app:layout_expandable="true" />
</net.cachapa.expandablelayout.ExpandableLinearLayout>
После того, как вы отметите ваши расширяемые представления, просто вызовите любой из этих методов в контейнере: expand()
, collapse()
или же toggle()
Я знаю, что прошло много времени с тех пор, как был опубликован оригинальный вопрос. Но я думаю, что для таких, как я, небольшое объяснение ответа @Heisenberg поможет.
Объявите две переменные в классе адаптера как
private int mExpandedPosition= -1;
private RecyclerView recyclerView = null;
Затем в onBindViewHolder, следуя указаниям оригинального ответа.
// This line checks if the item displayed on screen
// was expanded or not (Remembering the fact that Recycler View )
// reuses views so onBindViewHolder will be called for all
// items visible on screen.
final boolean isExpanded = position==mExpandedPosition;
//This line hides or shows the layout in question
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
// I do not know what the heck this is :)
holder.itemView.setActivated(isExpanded);
// Click event for each item (itemView is an in-built variable of holder class)
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// if the clicked item is already expaned then return -1
//else return the position (this works with notifyDatasetchanged )
mExpandedPosition = isExpanded ? -1:position;
// fancy animations can skip if like
TransitionManager.beginDelayedTransition(recyclerView);
//This will call the onBindViewHolder for all the itemViews on Screen
notifyDataSetChanged();
}
});
И, наконец, чтобы получить объект recyclerView в переопределении адаптера
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
Надеюсь это поможет.
Нет необходимости использовать сторонние библиотеки. Небольшая настройка метода, продемонстрированного в Google I/O 2016 и Гейзенберге на эту тему, делает свое дело.
поскольку notifyDataSetChanged()
перерисовывает полный RecyclerView
, notifyDataItemChanged()
это лучший вариант (не лучший), потому что у нас есть позиция и ViewHolder
в нашем распоряжении, и notifyDataItemChanged()
только перерисовывает ViewHolder
в данной позиции.
Но проблема в том, что преждевременное исчезновение ViewHolder
при нажатии и его появление не устраняется, даже если notifyDataItemChanged()
используется.
Следующий код не прибегает к notifyDataSetChanged()
или же notifyDataItemChanged()
и протестирован на API 23 и работает как шарм при использовании в RecyclerView, где каждый ViewHolder имеет CardView
поскольку это корневой элемент:
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final boolean visibility = holder.details.getVisibility()==View.VISIBLE;
if (!visibility)
{
holder.itemView.setActivated(true);
holder.details.setVisibility(View.VISIBLE);
if (prev_expanded!=-1 && prev_expanded!=position)
{
recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.setActivated(false);
recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.findViewById(R.id.cpl_details).setVisibility(View.GONE);
}
prev_expanded = position;
}
else
{
holder.itemView.setActivated(false);
holder.details.setVisibility(View.GONE);
}
TransitionManager.beginDelayedTransition(recycler);
}
});
prev_position
является глобальным целым числом, инициализированным -1. details
это полное представление, которое отображается в развернутом виде и скрыто при свертывании.
Как сказано, корневой элемент ViewHolder
это CardView
с foreground
а также stateListAnimator
атрибуты определены точно так, как сказал Гейзенберг по этой теме.
ОБНОВЛЕНИЕ: вышеупомянутая демонстрация свернет ранее развернутый элемент, если один из них развернут. Чтобы изменить это поведение и сохранить развернутый элемент таким, какой он есть, даже когда другой элемент развернут, вам потребуется следующий код.
if (row.details.getVisibility()!=View.VISIBLE)
{
row.details.setVisibility(View.VISIBLE);
row.root.setActivated(true);
row.details.animate().alpha(1).setStartDelay(500);
}
else
{
row.root.setActivated(false);
row.details.setVisibility(View.GONE);
row.details.setAlpha(0);
}
TransitionManager.beginDelayedTransition(recycler);
ОБНОВЛЕНИЕ: при развертывании последних элементов в списке, он может не отображаться в полной видимости, потому что расширенная часть находится ниже экрана. Чтобы получить полный элемент на экране, используйте следующий код.
LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
int distance;
View first = recycler.getChildAt(0);
int height = first.getHeight();
int current = recycler.getChildAdapterPosition(first);
int p = Math.abs(position - current);
if (p > 5) distance = (p - (p - 5)) * height;
else distance = p * height;
manager.scrollToPositionWithOffset(position, distance);
ВАЖНО: чтобы вышеприведенные демонстрации работали, в их коде необходимо сохранить экземпляр RecyclerView и его LayoutManager (последний для гибкости)
После использования рекомендованного способа реализации расширяемых / складных элементов, находящихся в RecyclerView
на RecyclerView развернуть / свернуть элементы, на которые ответил Heisenberg, я видел некоторые заметные артефакты всякий раз, когда RecyclerView
обновляется путем вызова TransitionManager.beginDelayedTransition(ViewGroup)
и впоследствии notifyDatasetChanged()
,
Его оригинальный ответ:
final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1 : position;
TransitionManager.beginDelayedTransition(recyclerView);
notifyDataSetChanged();
}
});
Изменен:
final boolean isExpanded = position == mExpandedPosition;
holder.details.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mExpandedHolder != null) {
mExpandedHolder.details.setVisibility(View.GONE);
notifyItemChanged(mExpandedPosition);
}
mExpandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
mExpandedHolder = isExpanded ? null : holder;
notifyItemChanged(holder.getAdapterPosition());
}
}
- детали - это вид, который вы хотите показать / скрыть во время раскрытия / сворачивания элемента
- mExpandedPosition является
int
который отслеживает расширенный элемент - mExpandedHolder является
ViewHolder
используется во время разрушения предмета
Обратите внимание, что метод TransitionManager.beginDelayedTransition(ViewGroup)
а также notifyDataSetChanged()
заменены на notifyItemChanged(int)
нацеливаться на конкретный предмет и некоторые небольшие хитрости.
После модификации предыдущие нежелательные эффекты должны исчезнуть. Тем не менее, это не может быть идеальным решением. Он делал только то, что хотел, устраняя глазные раны.
::РЕДАКТИРОВАТЬ::
Для уточнения, оба mExpandedPosition
а также mExpandedHolder
являются глобальными.
Я удивлен, что пока нет краткого ответа, хотя такой анимации развертывания / свертывания очень легко добиться всего двумя строчками кода:
(recycler.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false // called once
вместе с
notifyItemChanged(position) // in adapter, whenever a child view in item's recycler gets hidden/shown
Так что для меня пояснения в приведенной ниже ссылке были действительно полезны: https://medium.com/@nikola.jakshic/how-to-expand-collapse-items-in-recyclerview-49a648a403a6
Вы можете использовать ExpandableLayout. https://github.com/KyoSherlock/ExpandableLayout
Этот ExpandableLayout похож на плавную анимацию развертывания / сворачивания CheckBox, поэтому его можно использовать где угодно (ListView или RecyclerView).
Выполните следующие действия после установки прослушивателя onClick для класса ViewHolder:
@Override
public void onClick(View v) {
final int originalHeight = yourLinearLayout.getHeight();
animationDown(YourLinearLayout, originalHeight);//here put the name of you layout that have the options to expand.
}
//Animation for devices with kitkat and below
public void animationDown(LinearLayout billChoices, int originalHeight){
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if (!billChoices.isShown()) {
billChoices.setVisibility(View.VISIBLE);
billChoices.setEnabled(true);
valueAnimator = ValueAnimator.ofInt(0, originalHeight+originalHeight); // These values in this method can be changed to expand however much you like
} else {
valueAnimator = ValueAnimator.ofInt(originalHeight+originalHeight, 0);
Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out
a.setDuration(200);
// Set a listener to the animation and configure onAnimationEnd
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
billChoices.setVisibility(View.INVISIBLE);
billChoices.setEnabled(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
// Set the animation on the custom view
billChoices.startAnimation(a);
}
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
billChoices.getLayoutParams().height = value.intValue();
billChoices.requestLayout();
}
});
valueAnimator.start();
}
}
Я думаю, что это должно помочь, вот как я реализовал и делает то же самое делает Google в представлении недавних вызовов.
//Global Variable
private int selectedPosition = -1;
@Override
public void onBindViewHolder(final CustomViewHolder customViewHolder, final int i) {
final int position = i;
final GetProductCatalouge.details feedItem = this.postBeanses.get(i);
customViewHolder.lly_main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectedPosition = i;
notifyDataSetChanged();
}
});
if (selectedPosition == i) {
if (customViewHolder.lly_hsn_code.getVisibility() == View.VISIBLE) {
customViewHolder.lly_hsn_code.setVisibility(View.GONE);
customViewHolder.lly_sole.setVisibility(View.GONE);
customViewHolder.lly_sole_material.setVisibility(View.GONE);
} else {
customViewHolder.lly_hsn_code.setVisibility(View.VISIBLE);
customViewHolder.lly_sole.setVisibility(View.VISIBLE);
customViewHolder.lly_sole_material.setVisibility(View.VISIBLE);
}
} else {
customViewHolder.lly_hsn_code.setVisibility(View.GONE);
customViewHolder.lly_sole.setVisibility(View.GONE);
customViewHolder.lly_sole_material.setVisibility(View.GONE);
}
}
Используйте два типа просмотра в вашем RVAdapter
, Один для расширенного макета, а другой для свернутого. И волшебство происходит с установкой android:animateLayoutChanges="true"
за RecyclerView
Проверьте эффект, достигнутый с помощью этого в 0:42 в этом видео
Это легко, просто и идеально подходит для меня, надеюсь, это поможет:
Во-первых: добавьте это в группу просмотра элемента recyclerview
android:animateLayoutChanges="true"
Во-вторых: добавьте это в свой метод onclick, который разворачивает или сворачивает текущий элемент:
val changeBounds = ChangeBounds()
// binder.contraintLayout is viewGroup that have animateLayoutChange = true (iam using databinding)
changeBounds.duration = binder.contrainLayout.layoutTransition.getDuration(LayoutTransition.CHANGING)
changeBounds.interpolator = binder.contrainLayout.layoutTransition.getInterpolator(LayoutTransition.CHANGING)
binder.expandView.Visibility = binder.expandView.Visibility != View.Visible
TransitionManager.beginDelayedTransition(binder.root.parent as ViewGroup, changeBounds)
Создайте свой собственный пользовательский вид
- Если вы хотите, вы можете создать свой собственный многоразовый пользовательский вид. Вот быстрый и грязный пример сообщения в блоге, которое я сделал по этому вопросу. Ссылки отмечены в сообщении блога