Список, що розширюється за допомогою RecyclerView?


95

Можна використовувати елементи списку, що розширюються, за допомогою нового RecyclerView? Подобається ExpandableListView?


1
ви можете побачити годинник android у google source на android 6.0
zys

@zys Де я можу знайти вихідний код цього прикладу для годинника Android?
AppiDevo

1
Ви можете використовувати інший viewType, щоб завантажити інший макет, натиснувши кнопку розгортання. Це рішення використовується Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys

див. мою просту відповідь stackoverflow.com/a/48092441/5962715
Кіран Бенні Джозеф

Відповіді:


121

Це просто зробити із стандартним LayoutManagers, все залежить від того, як ви керуєте своїм адаптером.

Коли ви хочете розгорнути розділ, ви просто додаєте нові елементи до свого адаптера після заголовка. Не забудьте зателефонувати до notifyItemRangeInserted, коли ви це робите. Щоб згорнути розділ, ви просто видаляєте відповідні елементи та викликаєте notifyItemRangeRemoved (). Для будь-яких змін даних, про які повідомлено належним чином, подання утилізатора анімує подання. При додаванні предметів робиться область, яку потрібно заповнити новими предметами, при цьому нові предмети зникають. Видалення відбувається навпаки. Все, що вам потрібно зробити, крім матеріалів адаптера, - це створити стилі своїх поглядів, щоб передати логічну структуру користувачеві.

Оновлення: Райан Брукс написав статтю про те, як це зробити.


Чудова пропозиція. Дивно, чому ніхто інший не підтримав цю відповідь !!
x-treme

Я побачу, як додати це як приклад до SuperSLiM як частину наступного випуску.
Тонік Артос

Зараз Райан Брукс написав статтю про те, як це зробити.
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

Ця бібліотека має реалізацію розширюваного списку з переглядом recicler (див. Демонстраційний додаток у розділі "CardViewNative" -> "Список, сітка та RecyclerView" -> "Розширювані картки"). Він також має безліч інших цікавих комбінацій карт / списків.


2
Цей список розширюваних карток не є потомком RecyclerView (він не поширює RecyclerView, він лише розширює
ExpandableListVIew

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

Це зразок коду для того, що згадується @TonicArtos для додавання та видалення елементів та анімації його під час, це взято з RecyclerView Animations та зразка GitHub

1) Додайте слухача до свого 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 перебуває не в тій самій діяльності, що і Recycler View , передайте екземпляр recyclerView в адаптер під час створення

5) itemList - це ArrayList типу mObject, який допомагає підтримувати стан елемента (Відкрити / Закрити), ім'я, тип Елемента (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.