Расширяемый список с помощью RecyclerView?


95

Можно ли использовать расширяемые элементы списка с новым RecyclerView? Нравится ExpandableListView?


1
Вы можете увидеть часы Android в исходном коде Google на Android 6.0
zys

@zys Где я могу найти исходный код этого примера часов Android?
AppiDevo

1
Вы можете использовать другой viewType для загрузки другого макета, когда вы нажимаете кнопку «Развернуть». Это решение используется Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys


Ответы:


121

Это просто сделать с помощью стандартных LayoutManager, все зависит от того, как вы управляете своим адаптером.

Если вы хотите расширить раздел, вы просто добавляете новые элементы в свой адаптер после заголовка. Не забудьте при этом вызвать notifyItemRangeInserted. Чтобы свернуть раздел, вы просто удаляете соответствующие элементы и вызываете notifyItemRangeRemoved (). Для любых изменений данных, о которых сообщается должным образом, представление ресайклера анимирует представления. При добавлении элементов создается область для заполнения новыми элементами, при этом новые элементы постепенно появляются. Удаление происходит наоборот. Все, что вам нужно сделать помимо материала адаптера, - это стилизовать ваши представления, чтобы передать логическую структуру пользователю.

Обновление: Райан Брукс написал статью о том, как это сделать.


Отличное предложение. Интересно, почему никто не поддержал этот ответ !!
x-treme

Я подумаю о добавлении этого в качестве примера в SuperSLiM в следующем выпуске.
Tonic Artos

Райан Брукс написал статью о том, как это сделать.
Tonic Artos

Отличное предложение. Почему этот ответ находится так далеко вниз по странице, что мне нужно прокрутить вниз, чтобы найти его? Он должен быть показан на самом верху, чтобы большему количеству людей было легче найти этот красивый ответ.
RestInPeace

1
Райан Брукс пометил свою библиотеку как устаревшую. Интересно, просто ли он перестал поддерживать это, или оказывается, что такой подход что-то ломает, или создает утечки памяти, или что-то в этом роде ...
Варвара Калинина

6

Получите пример реализации кода отсюда

Установите ValueAnimator внутри onClick ViewHolder

@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();

    }
}

11
Это не работает «как ExpandableListView», потому что расширенное содержимое в этом случае представляет собой сам список с элементами, поступающими от адаптера. Это вырожденное решение, в котором в качестве детей в группе разрешен только 1 элемент.
TWiStErRob

он также не работает должным образом с представлениями, которые вообще не используются повторно
Takecare

Это не работает должным образом, так как представление в RecyclerList может повторяться несколько раз, поэтому, когда вы раскрываете один элемент, вы видите, что несколько элементов в списке раскрываются
Хоссейн Шахдуст,

3

https://github.com/gabrielemariotti/cardslib

В этой библиотеке реализована реализация расширяемого списка с помощью recyclerview (см. Демонстрационное приложение в разделе «CardViewNative» -> «Список, сетка и RecyclerView» -> «Расширяемые карты»). Также в нем есть много других интересных комбинаций карт / списков.


2
Этот список расширяемых карточек не является дочерним по отношению к RecyclerView (он не расширяет RecyclerView, он только расширяет ExpandableListVIew)
whizzzkey

0

Кто-то жаловался на то, что вышеупомянутое решение нельзя использовать со списком в качестве расширяемого содержимого. Но есть простое решение: создать список и заполнить его вручную своими строками. .

Решение для ленивых: есть простое решение, если вы не хотите сильно менять код. Просто используйте адаптер вручную для создания представлений и добавления их в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

Кавин Варнан уже научился анимировать макет ... Но если вы хотите использовать мой класс, не стесняйтесь, я опубликовал суть: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
«Ленивое решение» - действительно плохая идея. Добавление представлений в линейный макет внутри прокручиваемого представления настолько неэффективно.
Мэтью

Я думаю, что это по крайней мере более чем полезно. Работает быстро и плавно на всех устройствах, которые я тестировал. Между прочим, представления добавляются, когда представление списка не отображается ... Впоследствии будет отображаться только уже заполненное представление списка ...
prom85

Это было здорово! Большое спасибо
Паван Кумар

1
Как сказал @Matthew, это действительно плохая идея. Наличие одного большого прокручиваемого представления с LinearLayouts плохо масштабируется. Одна из главных причин, по которой RecyclerView / ListView и другие подобные представления были написаны, заключалась в том, чтобы оптимизировать использование больших списков неизвестного размера с поддержкой данных. Создание единого представления с кучей добавленных представлений отбрасывает все предоставленные оптимизации. Повторное использование представлений является огромным преимуществом и делает память вашего приложения более эффективной. Если количество элементов невелико, можно сэкономить много работы, используя списки для обработки привязки представлений.
munkay

Вы совершенно правы, конечно, это не идеально и ничего не оптимизирует ... Для моих сценариев использования у меня всегда было только несколько элементов ... Так что это не было проблемой ... Между прочим, тем временем я нашел способ для вложенных представлений ресайклера ... Просто используйте горизонтальный вид ресайклера фиксированной высоты (не идеален для каждого варианта использования, но все же) в качестве вложенного, recyclerviewи вы можете развернуть / скрыть этот вложенный и использовать все оптимизацииrecyclerview
prom85

0

Вы можете использовать ExpandableLayout, который похож на плавную анимацию развертывания / свертывания CheckBox, поэтому вы можете использовать его как CheckBox в ListView и RecyclerView.

https://github.com/KyoSherlock/ExpandableLayout


0

Это пример кода для того, что упоминается @TonicArtos для добавления и удаления элементов, а также для его анимации при выполнении, это взято из RecyclerView Animations и образца GitHub

1) Добавьте Listener в свой 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 адаптеру при создании

5) itemList - это список ArrayList типа mObject, который помогает поддерживать состояния элемента (Open / Close), имя, тип элемента (subItems / mainItem) и устанавливать тему на основе значений

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.