RecyclerView实现可扩展列表?

98

是否可以在新的RecyclerView中使用可展开的列表项?类似于ExpandableListView?


1
你可以在Android 6.0上的Google源代码中看到Android时钟。 - zys
@zys 我在哪里可以找到这个Android时钟示例源代码? - AppiDevo
1
当您点击展开按钮时,您可以使用不同的 viewType 来加载不同的布局。这个解决方案被 Android 时钟所使用:https://android.googlesource.com/platform/packages/apps/DeskClock/ - zys
请查看我的简单答案 https://dev59.com/aF4d5IYBdhLWcg3wE_KP#48092441 - Kiran Benny Joseph
对于两个级别:https://thoughtbot.com/blog/introducing-expandablerecyclerview。对于三个或更多级别:https://blog.usejournal.com/multi-level-expandable-recycler-view-e75cf1f4ac4b?gi=8f971378ece0,https://github.com/bmelnychuk/AndroidTreeView,https://karthicandroid.blogspot.com/2016/08/shopping-navigation-list-with-three.html。 - CoolMind
6个回答

129

使用默认的LayoutManager来实现这个很简单,一切都取决于你如何管理你的适配器。

当你想要展开一个部分时,只需在标题后面向适配器添加新条目。记得在这样做时调用notifyItemRangeInserted。要折叠一个部分,只需删除相关条目,并调用notifyItemRangeRemoved()。对于任何适当通知的数据更改,RecyclerView将以动画方式显示视图。添加条目时,会创建一个用新条目填充的区域,并淡入新条目。删除则是相反的。除了适配器之外,您所需做的就是为您的视图设置样式,以传达逻辑结构给用户。

更新:Ryan Brooks现在已经撰写了一篇关于如何实现这一点的文章


好建议。不知道为什么没有其他人点赞这个答案!! - x-treme
我会考虑将此作为下一个版本SuperSLiM的示例添加进去。 - Tonic Artos
Ryan Brooks现在写了一篇关于如何做到这一点的文章 - Tonic Artos
2
Ryan Brooks将他的库标记为已弃用。我想知道他是停止支持它还是发现这种方法会破坏某些东西或者造成内存泄漏之类的问题... - Varvara Kalinina
@VarvaraKalinina,是的,Варвара,只有https://github.com/thoughtbot/expandable-recycler-view(他的存储库中的链接)才重要。他的项目和Groopie都不合适。 - CoolMind
显示剩余4条评论

4
这里获取示例代码实现。
在ViewHolder的onClick中设置ValueAnimator。
@Override
public void onClick(final View view) {
    if (mOriginalHeight == 0) {
        mOriginalHeight = view.getHeight();
    }
    ValueAnimator valueAnimator;
    if (!mIsViewExpanded) {
        mIsViewExpanded = true;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
    } else {
        mIsViewExpanded = false;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
    }
    valueAnimator.setDuration(300);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer value = (Integer) animation.getAnimatedValue();
            view.getLayoutParams().height = value.intValue();
            view.requestLayout();
        }
    });
    valueAnimator.start();

}

这是最终的代码。
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView mFriendName;
    private int mOriginalHeight = 0;
    private boolean mIsViewExpanded = false;


    public ViewHolder(RelativeLayout v) {
        super(v);
        mFriendName = (TextView) v.findViewById(R.id.friendName);
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(final View view) {
        if (mOriginalHeight == 0) {
            mOriginalHeight = view.getHeight();
        }
        ValueAnimator valueAnimator;
        if (!mIsViewExpanded) {
            mIsViewExpanded = true;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
        } else {
            mIsViewExpanded = false;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
        }
        valueAnimator.setDuration(300);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                view.getLayoutParams().height = value.intValue();
                view.requestLayout();
            }
        });
        valueAnimator.start();

    }
}

14
这个方案不像 ExpandableListView 那样工作,因为在那种情况下,展开的内容本身是一个列表,其中的项来自适配器。这是一个退化的解决方案,允许在组内只有1个子项。 - TWiStErRob
也不适用于任何被回收利用的视图,并且无法正常工作。 - takecare
它在RecyclerList中作为视图时无法正常工作,因为一个项目可能会被重复多次,所以当您展开一个项目时,您会看到列表上有多个项目被展开。 - Hossein Shahdoost

3

https://github.com/gabrielemariotti/cardslib

这个库实现了一个可扩展的列表,使用RecyclerView(请参考“CardViewNative”下的演示应用程序-->“列表、网格和RecyclerView”-->“可扩展卡片”)。它还有许多其他酷炫的卡片/列表组合。

2
这个可扩展卡片列表不是RecyclerView的子类(它没有扩展RecyclerView,它只扩展了ExpandableListView)。 - whizzzkey

0
这是关于如何添加和删除项目并在执行时进行动画的代码示例,这是由@TonicArtos提到的,取自RecyclerView AnimationsGitHub sample1) 在您的onCreateViewHolder()中添加侦听器以注册onClick事件 2) 在适配器中创建自定义的OnClickListener
private View.OnClickListener mItemListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TextView tv = (TextView) v.findViewById(R.id.tvItems);
        String selected = tv.getText().toString();
        boolean checked = itemsList.get(recyclerView.getChildAdapterPosition(v)).isChecked();

        switch (selected){
            case "Item1":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;
            case "Item2":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;                 
            default:
                //In my case I have checkList in subItems,
                //checkItem(v);
                break;
        }
    }
};

3) 添加你的addItem()和deleteItem()函数

private void addItem(View view){
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION){
        navDrawItems.add(position+1,new mObject());
        navDrawItems.add(position+2,new mObject());
        notifyItemRangeInserted(position+1,2);
    }
}


private void deleteItem(View view) {
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION) {
        navDrawItems.remove(position+2);
        navDrawItems.remove(position+1);
        notifyItemRangeRemoved(position+1,2);
    }
}

4) 如果你的RecyclerViewAdapter不在同一个Activity中作为Recycler View,在创建时将recyclerView的实例传递给Adapter

5) itemList是一个mObject类型的ArrayList,它帮助维护项目的状态(打开/关闭),名称,项目类型(子项目/主项目)并根据值设置主题

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 

0

有人抱怨说以上提到的解决方案不能与ListView一起使用作为可展开内容。但是有一个简单的解决方案:创建一个ListView并手动填充行

懒人的解决方案:如果你不想改变太多代码,有一个简单的解决方案。 只需手动使用适配器来创建视图并将它们添加到LinearLayout中。

以下是示例:

if (mIsExpanded)
{
    // llExpandable... is the expandable nested LinearLayout
    llExpandable.removeAllViews();
    final ArrayAdapter<?> adapter = ... // create your adapter as if you would use it for a ListView
    for (int i = 0; i < adapter.getCount(); i++)
    {
        View item = adapter.getView(i, null, null);
        // if you want the item to be selectable as if it would be in a default ListView, then you can add following code as well:
        item.setBackgroundResource(Functions.getThemeReference(context, android.R.attr.selectableItemBackground));
        item.setTag(i);
        item.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // item would be retrieved with: 
                // adapter.getItem((Integer)v.getTag())
            }
        });
        llExpandable.addView(item);
    }
    ExpandUtils.expand(llExpandable, null, 500);
}
else
{
    ExpandUtils.collapse(llExpandable, null, 500);
}

辅助函数:getThemeReference

public static int getThemeReference(Context context, int attribute)
{
    TypedValue typeValue = new TypedValue();
    context.getTheme().resolveAttribute(attribute, typeValue, false);
    if (typeValue.type == TypedValue.TYPE_REFERENCE)
    {
        int ref = typeValue.data;
        return ref;
    }
    else
    {
        return -1;
    }
}

辅助类:ExpandUtils

Kavin Varnan已经发布了如何动画化布局的帖子... 但如果您想使用我的类,请随意使用,我发布了一个代码片段:https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
“懒惰的解决方案”是一个非常糟糕的想法。在可滚动视图中添加线性布局的视图非常低效。 - Matthew
我认为它至少比可用更多。在我测试过的所有设备上都快速流畅地工作。顺便提一下,当ListView不可见时添加视图...只有已经填充的ListView才会随后显示... - prom85
这很棒!非常感谢。 - Pawan Kumar
1
正如@Matthew所提到的,这真的不是一个好主意。使用单个大的带有LinearLayouts可滚动视图无法很好地扩展。RecyclerView/ListView和其他类似的视图被编写的最大原因之一就是为了优化具有未知大小的大型数据支持列表。构建一个添加了大量视图的单个视图会将提供的所有优化都抛弃。回收视图是一个巨大的优势,可以使您的应用程序内存高效。除非项目数量很少,否则使用列表处理视图绑定可以节省大量工作。 - munkay
你完全是正确的,当然这并不完美也没有优化什么...... 对于我的用例,我总是只有几项... 所以这不是问题... 顺便说一句,在此期间我找到了一种嵌套式Recycler视图的方法... 只需使用一个固定高度的水平Recycler视图(并不适用于每个用例,但仍然可以)作为嵌套的"recyclerview",并且您可以展开/隐藏此嵌套的项目,并使用"recyclerview"的所有优化。 - prom85

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接