RecyclerView展开/折叠项目

sta*_*ete 137 expand android android-layout recycler-adapter android-recyclerview

我想扩展/折叠我的recyclerView的项目,以显示更多信息.我想实现SlideExpandableListView的相同效果.

基本上在我的viewHolder中我有一个不可见的视图,我想做一个平滑的展开/折叠动画,而不是只将可见性设置为VISIBLE/GONE.我一次只需要一个项目进行扩展,如果有一个高程来显示该项目已被选中,那将会很酷.

它与新Android最近通话记录列表的效果相同.选项"CALL BACK"和"DETAILS"仅在选择项目时可见.

RecyclerView可扩展项目

Hei*_*erg 186

请不要使用任何库来实现此效果,而是根据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();
    }
});
Run Code Online (Sandbox Code Playgroud)
  • 其中的细节是我的,这将在触摸显示视图(下称你的情况的详细信息.默认Visibility.GONE).
  • mExpandedPosition是一个初始化为-1的int变量

对于您想要的炫酷效果,请将它们用作list_item属性:

android:background="@drawable/comment_background"
android:stateListAnimator="@animator/comment_selection"
Run Code Online (Sandbox Code Playgroud)

其中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>
Run Code Online (Sandbox Code Playgroud)

和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>
Run Code Online (Sandbox Code Playgroud)

  • 使用此代码时,展开动画看起来很奇怪.每当顶级项目消失,一切都向上移动一个位置.你有过类似的经历吗? (22认同)
  • 这是一个糟糕的方法.我无法相信它是在Google I/O上推荐的.即使在调查了Plaid项目并实现了所有必需的变通方法之后,动画仍会在RecyclerView中引起一些重大故障,例如,第一个/最后一个可见项目过早消失.在动画运行时滚动也会使所有事情变得混乱.一般来说,我不认为解决这种方法引起的所有问题可能被称为"只是放一行,一切都会正常工作". (20认同)
  • github.com/nickbutcher/plaid是谷歌示例项目吗?代码太乱了...... (6认同)
  • 我尝试做动画,但我在适配器上实现了这个,但是什么是`TransitionManager.beginDelayedTransition(recyclerView)的解决方案;`如果我不能使用回收器视图引用. (5认同)
  • 我们在2019年是否有其他更简便的方法解决此问题? (4认同)
  • @Ranveer它的工作原理。我有两个非常相似的recyclerViews,但仅能正常工作一个。不知道为什么。@MartínDaniel您需要在适配器的构造函数中将`recyclerView`作为参数传递 (2认同)
  • 我看到的最流畅的recyclerview展开/折叠动画在这里:https://proandroiddev.com/complex-ui-animation-on-android-8f7a46f4aec4 (2认同)

Moj*_*osh 70

不是说这是最好的方法,但似乎对我有用.

完整代码可在以下位置找到:示例代码: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>
Run Code Online (Sandbox Code Playgroud)

然后,使您的RV Adapter类实现View.OnClickListener,以便您可以对所单击的项目执行操作.添加一个int字段以保存一个展开视图的位置,并将其初始化为负值:

private int expandedPosition = -1;
Run Code Online (Sandbox Code Playgroud)

最后,实现ViewHolder,onBindViewHolder()方法并覆盖onClick()方法.如果它的位置等于"expandedPosition",你将在onBindViewHolder中展开视图,如果不是,则隐藏它.您在onClick侦听器中设置expandedPosition的值:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这应该一次只扩展一个项目,使用系统默认动画进行布局更改.至少它对我有用.希望能帮助到你.

  • 我应该覆盖哪个`onClick()`?适配器中没有`onClick`. (4认同)
  • @ user2731584 - 我不这么认为.看看(我相信)他们的来源,似乎Lollipop时钟正在使用ListView来显示警报列表.布局:https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-5.0.1_r1/res/layout/alarm_clock.xml他们似乎有自定义代码来动画展开/折叠项目名单.请参阅以下来源:https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-5.0.1_r1/src/com/android/deskclock/AlarmClockFragment.java (2认同)

Kir*_*eph 67

为此,只需要简单的线条并不复杂

在你的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);
    }
});
Run Code Online (Sandbox Code Playgroud)

mExpandedPosition是一个初始化为-1的int全局变量

对于那些只想要扩展一个项目而其他项目被折叠的人.用这个

首先使用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);
        }
    });
Run Code Online (Sandbox Code Playgroud)

完成!!! 简单而谦逊.. :)

  • 很好的解决方案。另外,我建议将设置 `OnClickListener` 移至 `onCreateViewHolder` 而不是 `onBindViewHolder` (2认同)
  • 工作得很好,大约是时候有一个不错的解决方案,让“RecyclerView”完成工作,我的意思是,“LayoutManager”的意义何在,否则如果它不管理布局!这个解决方案比投票最高的解决方案更好,因为它的代码更少,让“RecyclerView”处理事情并且工作得更好! (2认同)
  • 不太明白如何添加“详细信息”视图。每个持有者都应该是静态的吗?如果我需要为每个列表项添加不同的视图怎么办? (2认同)

Lau*_*nac 15

有一个非常简单的库使用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>
Run Code Online (Sandbox Code Playgroud)

当你标记你的扩张的观点,只是调用任何容器上这些方法:expand(),collapse()toggle()


小智 8

我知道自原始问题发布以来已经很长时间了.但我认为对于像我这样缓慢的人来说,@ Heisenberg的回答会有所帮助.

在适配器类中声明两个变量as

private int mExpandedPosition= -1;
private RecyclerView recyclerView = null;
Run Code Online (Sandbox Code Playgroud)

然后在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();
            }
        });
Run Code Online (Sandbox Code Playgroud)

最后,在适配器覆盖中获取recyclerView对象

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);

    this.recyclerView = recyclerView;
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 简单明了的解决方案.小改进,使用RecyclerView.NO_POSITION而不是幻数-1. (2认同)

Kou*_*ury 8

根本不需要使用第三方库.一个小调整的方法证明了谷歌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);              
        }
});
Run Code Online (Sandbox Code Playgroud)

prev_position是一个初始化为-1的全局整数. details是展开时显示的完整视图,折叠时显示隐形.

如上所述,根元素ViewHolder是一个CardViewwith foregroundstateListAnimator属性,完全按照海森堡关于这个主题所说的那样定义.

更新:如果其中一个扩展,上述演示将折叠previosuly扩展项目.要修改此行为并保持扩展项目,即使展开另一个项目,您还需要以下代码.

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);
Run Code Online (Sandbox Code Playgroud)

更新:扩展列表中的最后一项时,可能无法使其完全可见,因为展开的部分位于屏幕下方.要获取屏幕中的完整项目,请使用以下代码.

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);
Run Code Online (Sandbox Code Playgroud)

重要提示:要使上述演示工作,必须在其代码中保留RecyclerView及其LayoutManager的实例(后者的灵活性)


Cha*_*Wei 6

在使用建议的方法来实现驻留在RecyclerView上HeisenBerg回答的RecyclerView展开/折叠项目RecyclerView上的可扩展/可折叠项目之后,每当通过调用以及随后进行刷新时,我都看到了一些明显的工件。RecyclerViewTransitionManager.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();
    }
});
Run Code Online (Sandbox Code Playgroud)

改性:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 详细信息是您要在项目展开/折叠过程中显示/隐藏的视图
  • mExpandedPositionint跟踪扩展项的
  • mExpandedHolderViewHolder项目崩溃时使用

请注意,方法TransitionManager.beginDelayedTransition(ViewGroup)notifyDataSetChanged()被替换notifyItemChanged(int)为针对特定项目和一些小调整。

修改后,以前的不良影响应该消失了。但是,这可能不是完美的解决方案。它只做了我想要的,消除了眼神。

::编辑::

为了澄清起见,mExpandedPositionmExpandedHolder均为全局变量。