¿Lista ampliable con RecyclerView?

95

¿Es posible usar elementos de lista expandibles con el nuevo RecyclerView? ¿Te gusta ExpandableListView?

Dariusz Rusin
fuente
1
puede ver el reloj de Android en la fuente de Google en Android 6.0
zys
@zys ¿Dónde puedo encontrar este código fuente de ejemplo de reloj de Android?
AppiDevo
1
Puede usar un tipo de vista diferente para cargar un diseño diferente, cuando hace clic en el botón expandir.Esta solución es utilizada por Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys
vea mi respuesta simple stackoverflow.com/a/48092441/5962715
Kiran Benny Joseph

Respuestas:

121

Esto es fácil de hacer con los LayoutManagers de stock, todo depende de cómo gestione su adaptador.

Cuando desee expandir una sección, simplemente agregue nuevos elementos a su adaptador después del encabezado. Recuerde llamar a notifyItemRangeInserted cuando haga esto. Para contraer una sección, simplemente elimine los elementos relevantes y llame a notifyItemRangeRemoved (). Para cualquier cambio de datos que se notifique adecuadamente, la vista del reciclador animará las vistas. Al agregar elementos, se crea un área para rellenar con los nuevos elementos, y los nuevos elementos van apareciendo. La eliminación es lo contrario. Todo lo que necesita hacer además de las cosas del adaptador es diseñar sus vistas para transmitir la estructura lógica al usuario.

Actualización: Ryan Brooks ahora ha escrito un artículo sobre cómo hacer esto.

Artos tónicos
fuente
Gran sugerencia. ¡Me pregunto por qué nadie más votó a favor de esta respuesta!
x-treme
Veré agregar esto como un ejemplo a SuperSLiM como parte de la próxima versión.
Tonic Artos
Ryan Brooks ahora ha escrito un artículo sobre cómo hacer esto.
Tonic Artos
Gran sugerencia. ¿Por qué esta respuesta está tan abajo en la página que tengo que desplazarme hacia abajo para encontrarla? Debería mostrarse en la parte superior para que más personas puedan encontrar esta hermosa respuesta más fácilmente.
RestInPeace
1
Ryan Brooks marcó su biblioteca como obsoleta. Me pregunto si es solo que ha dejado de apoyarlo o resulta que este enfoque rompe algo o crea pérdidas de memoria o algo ...
Varvara Kalinina
6

Obtenga la implementación del código de muestra desde aquí

Establecer ValueAnimator dentro de onClick de 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();

}

Aquí está el código final

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

    }
}
Kavin Varnan
fuente
11
Esto no funciona "como ExpandableListView", porque el contenido expandido en ese caso es una lista en sí misma con elementos que provienen del adaptador. Esta es una solución degenerada con solo 1 elemento permitido como niños dentro del grupo.
TWiStErRob
tampoco funciona correctamente con vistas que se reciclan en absoluto
takecare
No funciona correctamente ya que una vista en RecyclerList puede repetirse varias veces, por lo que cuando expande un elemento, verá que se expanden varios elementos en la lista
Hossein Shahdoost
3

https://github.com/gabrielemariotti/cardslib

Esta biblioteca tiene una implementación de una lista expandible con una vista de reciclaje (consulte la aplicación de demostración en "CardViewNative" -> "Lista, cuadrícula y RecyclerView" -> "Tarjetas expandibles"). También tiene muchas otras combinaciones interesantes de cartas / listas.

Xinzz
fuente
2
Esta lista de tarjetas expandibles no es un elemento secundario de RecyclerView (no extiende RecyclerView, solo extiende ExpandableListVIew)
whizzzkey
0

Alguien se quejó de que la solución mencionada anteriormente no se puede utilizar con una vista de lista como contenido expandible. Pero hay una solución simple: cree una vista de lista y complete esta vista de lista manualmente con sus filas .

Solución para los perezosos: hay una solución simple si no quieres cambiar mucho tu código. Simplemente use su adaptador manualmente para crear vistas y agréguelas al archivo LinearLayout.

Este es el ejemplo:

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);
}

funciones de ayuda: 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;
    }
}

clase auxiliar: ExpandUtils

Kavin Varnan ya publicó cómo animar un diseño ... Pero si quieres usar mi clase, no dudes en hacerlo, publiqué una esencia: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a

prom85
fuente
9
La "solución perezosa" es una muy mala idea. Agregar vistas a un diseño lineal dentro de una vista desplazable es tan ineficaz.
Mateo
Creo que al menos es más que utilizable. Funciona rápido y sin problemas en todos los dispositivos que lo probé. Por cierto, las vistas se agregan cuando la vista de lista no es visible ... luego solo se mostrará la vista de lista ya llena ...
prom85
¡Esto fue genial! Muchísimas gracias
Pawan Kumar
1
Como mencionó @Matthew, esto realmente no es una buena idea. Tener una sola vista desplazable grande con LinearLayouts no se escala bien. Una de las razones más importantes por las que se escribieron RecyclerView / ListView y otras vistas similares fue para optimizar el hecho de tener grandes listas respaldadas por datos de tamaño desconocido. La creación de una vista única con un montón de vistas agregadas elimina todas las optimizaciones proporcionadas. Reciclar vistas es un gran beneficio y hace que la memoria de su aplicación sea eficiente. A menos que la cantidad de elementos sea pequeña, se ahorra mucho trabajo al usar listas para manejar la vinculación de vistas.
munkay
Tienes toda la razón, por supuesto que esto no es perfecto y no optimiza nada ... Para mis casos de uso, siempre tuve solo algunos elementos ... Así que esto no fue un problema ... Por cierto, mientras tanto encontré una manera para vistas de reciclador anidadas ... Simplemente use una vista de reciclador horizontal de altura fija (no es perfecta para todos los casos de uso, pero aún así) como anidada recyclerviewy puede expandir / ocultar esta anidada y usar todas las optimizaciones delrecyclerview
prom85
0

Este es el código de muestra de lo que @TonicArtos menciona para agregar y eliminar elementos y animarlo mientras lo hace, esto se toma de la muestra de RecyclerView Animations y GitHub

1) Agregue Listener dentro de su onCreateViewHolder () para registrarse en onClick

2) Cree su OnClickListener personalizado dentro de su adaptador

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) Agregue su addItem () y 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) Si su RecyclerViewAdapter no está en la misma actividad que Recycler View , pase la instancia de recyclerView al adaptador mientras crea

5) itemList es una ArrayList de tipo mObject que ayuda a mantener los estados del elemento (Abrir / Cerrar), nombre, tipo de elemento (subItems / mainItem) y establecer el tema en función de los valores

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