RecyclerView 展开/折叠项目
Posted
技术标签:
【中文标题】RecyclerView 展开/折叠项目【英文标题】:RecyclerView expand/collapse items 【发布时间】:2015-01-28 00:11:15 【问题描述】:我想展开/折叠我的 recyclerView 的项目以显示更多信息。我想达到与SlideExpandableListView 相同的效果。
基本上在我的 viewHolder 中,我有一个不可见的视图,我想做一个平滑的展开/折叠动画,而不是将可见性设置为 VISIBLE/GONE。我一次只需要展开一个项目,如果有一些高度来显示该项目已被选中,那就太酷了。
与新的 android 最近通话记录列表效果相同。选项“CALL BACK”和“DETAILS”仅在选择项目时可见。
【问题讨论】:
我有一个相关的问题。在时钟应用程序中,闹钟设置活动具有类似的功能。是不是也一样? 嘿,你有解决办法吗?我也在为我的应用寻找相同的解决方案。 你找到解决办法了吗? 我已根据 Google I/O 2016 更新了推荐的操作方式。请参阅我的回答 ***.com/questions/27203817/… 在这里查看我的答案 一个简单而酷的解决方案***.com/a/48092441/5962715 【参考方案1】:并不是说这是最好的方法,但它似乎对我有用。
完整的代码可以在以下位置找到: 示例代码位于:https://github.com/dbleicher/recyclerview-grid-quickreturn
首先,将扩展区域添加到您的单元格/项目布局中,并使封闭单元格布局 animateLayoutChanges="true"。这将确保展开/折叠动画:
<LinearLayout
android:id="@+id/llCardBack"
android:layout_
android:layout_
android:background="@android:color/white"
android:animateLayoutChanges="true"
android:padding="4dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_
android:layout_
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_
android:layout_
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_
android:layout_
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_
android:layout_
android:layout_margin="6dp"
android:text="Item One" />
<TextView
android:layout_
android:layout_
android:layout_margin="6dp"
android:text="Item Two" />
</LinearLayout>
</LinearLayout>
然后,让您的 RV 适配器类实现 View.OnClickListener 以便您可以对被单击的项目进行操作。添加一个 int 字段来保存一个展开视图的位置,并将其初始化为负值:
private int expandedPosition = -1;
最后,实现您的 ViewHolder、onBindViewHolder() 方法并覆盖 onClick() 方法。如果它的位置等于“expandedPosition”,您将在 onBindViewHolder 中展开视图,否则将其隐藏。您在 onClick 监听器中设置 expandPosition 的值:
@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);
这应该一次只展开一项,使用系统默认动画来改变布局。至少它对我有用。希望对您有所帮助。
【讨论】:
我知道如何只选择一项。我真的不知道如何在该项目上制作动画。使用默认系统动画的展开/折叠动画看起来不是很流畅。 我还没有尝试以下方法,但它看起来确实可以。查看答案:***.com/a/26443762/2259418 @user2731584 - 我不这么认为。查看(我相信是)他们的来源,Lollipop 时钟似乎正在使用 ListView 来显示警报列表。布局:android.googlesource.com/platform/packages/apps/DeskClock/+/… 他们似乎有自定义代码来动画此列表中的展开/折叠项目。请参阅以下来源:android.googlesource.com/platform/packages/apps/DeskClock/+/… @MojoTosh 你是对的。他们正在使用 ListView 并使用代码制作动画。AlarmClockFragment.java
文件中的expandAlarm
函数执行扩展部分。
我应该覆盖哪个onClick()
?适配器中没有onClick
。【参考方案2】:
将 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();
我认为这应该会有所帮助,这就是我实施的方式,并且在最近的通话视图中与谷歌所做的相同。
【讨论】:
【参考方案3】:有一个非常简单易用的支持 gradle 的库:https://github.com/cachapa/ExpandableLayout。
来自库文档:
<net.cachapa.expandablelayout.ExpandableLinearLayout
android:id="@+id/container"
android:layout_
android:layout_
app:el_duration="1000"
app:el_expanded="true">
<TextView
android:layout_
android:layout_
android:text="Click here to toggle expansion" />
<TextView
android:layout_
android:layout_
android:text="Fixed height"
app:layout_expandable="true" />
</net.cachapa.expandablelayout.ExpandableLinearLayout>
标记可扩展视图后,只需在容器上调用以下任何方法:expand()
、collapse()
或 toggle()
【讨论】:
这仍然是最好的方法吗?我正在努力遵循您所写的内容。我有一些回收视图,我想在点击时展开以显示图表【参考方案4】:请不要使用任何库来实现此效果,而是使用 Google I/O 推荐的方法。在您的 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();
);
details 是我的视图,将在触摸时显示(在您的情况下为通话详细信息。默认 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>
【讨论】:
使用此代码时,展开动画看起来很奇怪。每次顶部项目消失并且所有内容都向上移动一个位置。你有过类似的经历吗? 我尝试制作动画,但我在适配器上实现了这个,但如果我无法获得回收站视图参考,TransitionManager.beginDelayedTransition(recyclerView);
的解决方案是什么。
@Ranveer 有点用。我有两个非常相似的 recyclerViews,但它只适用于一个。不知道为什么。 @MartínDaniel 您需要在适配器的构造函数中将recyclerView
作为参数传递
这是一种糟糕的方法。我不敢相信它在 Google I/O 上被推荐。即使在调查了 Plaid 项目并从中实现了所有必需的解决方法之后,动画仍然会导致 RecyclerView 中出现一些重大故障,例如第一个/最后一个可见项目过早消失。在动画运行时滚动也会把所有事情搞得一团糟。总的来说,我不认为解决所有由这种方法引起的问题可能被称为“只要放一条线,一切都会正常工作”。
2019年我们有没有更新鲜、更简单的方法来解决这个问题?【参考方案5】:
在您的RVAdapter
中使用两种视图类型。一种用于展开布局,另一种用于折叠。
魔术发生在将android:animateLayoutChanges="true"
设置为RecyclerView
在this video 0:42 查看使用此实现的效果
【讨论】:
介意解释一下吗?我似乎无法让它工作 @sourlemonaid 基本上你为列表创建多个视图类型,如***.com/a/26245463/3731795 中所述...你跟踪项目展开的索引,其余项目折叠在那时。现在 if(current_item_index == expand_item_index) return VIEW_TYPE_EXPANDED else reutrn VIEW_TYPE_COLLAPSED 【参考方案6】:为此,只需要简单的线条而不是复杂的
在您的 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);
);
【讨论】:
很好的解决方案。另外我建议将设置OnClickListener
s 移动到 onCreateViewHolder
而不是 onBindViewHolder
工作得很好,大约是时候有一个体面的解决方案让RecyclerView
完成工作,我的意思是,LayoutManager
的意义何在,否则如果它不管理布局!这个解决方案比投票最多的解决方案更好,因为它的代码更少,让RecyclerView
处理事情并且工作得更好!
不太明白如何添加“详细信息”视图。每个持有人都应该是静态的吗?如果我需要为每个列表项添加不同的视图怎么办?
@BekaBot 这是您在折叠项目时要隐藏的视图
@KiranBennyJoseph 是的,我明白了。在这种情况下,所有项目的视图都应该相同吗?如果我想要不同项目的不同视图怎么办?【参考方案7】:
使用推荐的方法来实现驻留在RecyclerView expand/collapse items 上的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());
details 是您想要在项目展开/折叠期间显示/隐藏的视图
mExpandedPosition 是一个 int
,用于跟踪展开的项目
mExpandedHolder 是在项目折叠期间使用的 ViewHolder
请注意,TransitionManager.beginDelayedTransition(ViewGroup)
和 notifyDataSetChanged()
方法被替换为 notifyItemChanged(int)
以针对特定项目和一些小调整。
修改后,之前不需要的效果应该没有了。但是,这可能不是完美的解决方案。它只做了我想做的事,消除了碍眼的东西。
::EDIT::
为了澄清,mExpandedPosition
和 mExpandedHolder
都是全局变量。
【讨论】:
请说明 mExpandedHolder 是如何以及在何处声明和使用的? @Werner 我已经编辑了答案以显示它的声明位置。用法在 sn-p 本身以及目的之内。它用于为折叠、展开的项目设置动画。 就动画而言,我最喜欢这种方法,但是单击该项目时我会出现这种小“淡入淡出”。您是否也有这个或不同的说法,这是由于使用 notifyItemChanged() 造成的吗? drive.google.com/file/d/1GaU1fcjHEfSErYF50otQGLGsIWkSyQSe/… 是的。就像你猜到的,它是由notifyItemChanged()
引起的。您可以手动设置动画并更改项目高度并在其中添加视图。
@ChanTeckWei:您的解决方案运行良好,但每当我折叠或展开 recyclerview 项目行时,行图像就会闪烁。有什么解决方案吗?【参考方案8】:
我知道自从最初的问题发布以来已经有很长时间了。但我认为对于像我这样慢的人来说,对@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;
希望对您有所帮助。
【讨论】:
伟大而简单的解决方案。小改进,使用 RecyclerView.NO_POSITION 而不是幻数 -1。【参考方案9】:根本不需要使用第三方库。 对 Google I/O 2016 和 Heisenberg 就该主题演示的方法进行一点调整,就可以解决问题。
由于notifyDataSetChanged()
重绘了完整的RecyclerView
,notifyDataItemChanged()
是一个更好的选择(不是最好的),因为我们有位置和ViewHolder
供我们使用,还有@987654325 @ only 在给定位置重绘特定的ViewHolder
。
但问题是ViewHolder
在点击时过早消失,即使使用notifyDataItemChanged()
也没有消除它的出现。
以下代码不诉诸 notifyDataSetChanged()
或 notifyDataItemChanged()
,并在 API 23 上进行了测试,并且在每个 ViewHolder 都有一个 CardView
的 RecyclerView 上使用时就像一个魅力根元素:
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(后者是为了灵活性)
【讨论】:
感谢这个解决方案对我有用。我尽量避免使用notifyItemChanged
,因为我有一个ImageView
,它从一个url 加载图像。 notifyItemChanged
重新加载我的图片让它看起来很奇怪
确定它的行是TransitionManager.beginDelayedTransition(recycler);
,很棒的发现。但是我发现,通过这种方法,如果我按顺序点击几行垃圾邮件,最终会触发内部崩溃The specified child already has a parent. You must call removeView() on the child's parent first
。我想这是由于回收站视图边界上的视图回收,但我不能确定。有人遇到同样的问题吗?
这仍然是最好的方法吗?我正在努力遵循您所写的内容。我有一些回收视图,我想在单击时展开以显示图表。我很难理解到底该怎么做
有人有这个实现的完整例子吗?我的意思是一个工作代码示例。请帮忙。我不明白该怎么做?【参考方案10】:
//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);
【讨论】:
【参考方案11】:我很惊讶还没有简明的答案,虽然这样的展开/折叠动画很容易实现,只需要 2 行代码:
(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
【讨论】:
它非常简单且易于实现。但是对于回收站视图的最后一项,展开动画似乎被破坏了。 很棒的答案谢谢哥们。非常感谢您的回答。【参考方案12】:这很简单,对我来说很完美,希望对你有帮助:
首先:将此添加到 item recyclerview 的视图组中
android:animateLayoutChanges="true"
第二:将此添加到展开或折叠当前项目的 onclick 方法中:
val changeBounds = ChangeBounds()
changeBounds.duration = binder.contrainLayout.layoutTransition.getDuration(LayoutTransition.CHANGING)
changeBounds.interpolator = binder.contrainLayout.layoutTransition.getInterpolator(LayoutTransition.CHANGING)
TransitionManager.beginDelayedTransition(binder.root.parent as ViewGroup, changeBounds)
【讨论】:
以上是关于RecyclerView 展开/折叠项目的主要内容,如果未能解决你的问题,请参考以下文章
recyclerview 中的 Webview 没有就地展开。 (底部没有缩小)
RecyclerView 在 BottomSheetFragment 中隐藏其下方的项目