Credo che l'animazione non funzionava perché non si può animare LayoutParams
in quel modo anche se sarebbe pulito se si potesse. Ho provato il codice che avevi e tutto ciò che ha fatto è stato far saltare la mia vista alla nuova altezza. L'unico modo che ho trovato per farlo funzionare era usare uno ValueAnimator
come puoi vedere nell'esempio qui sotto.
Ho notato alcune carenze quando si utilizza DefaultItemAnimator
per mostrare/nascondere una vista aggiornando la sua visibilità. Sebbene abbia fatto spazio per la nuova vista e animato il resto degli elementi su e giù in base alla visibilità della vista espandibile, ho notato che non animava l'altezza della vista espandibile. Semplicemente sbiadito nel posto e fuori luogo usando solo il valore alfa.
Di seguito è riportato un numero personalizzato ItemAnimator
con animazioni di dimensione e alfa in base al fatto di nascondere/mostrare un LinearLayout
nel layout ViewHolder
. Inoltre permette il riutilizzo degli stessi ViewHolder
e tentativi di manipolazione animazioni parziali correttamente se l'utente tocca l'intestazione rapidamente:
public static class MyAnimator extends DefaultItemAnimator {
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, AnimatorState> animatorMap = new HashMap<>();
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
final ValueAnimator heightAnim;
final ObjectAnimator alphaAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final View expandableView = vh.getExpandableView();
final int toHeight; // save height for later in case reversing animation
if(vh.isExpanded()) {
expandableView.setVisibility(View.VISIBLE);
// measure expandable view to get correct height
expandableView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
toHeight = expandableView.getMeasuredHeight();
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 1f);
} else {
toHeight = 0;
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 0f);
}
heightAnim = ValueAnimator.ofInt(expandableView.getHeight(), toHeight);
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
expandableView.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
expandableView.requestLayout();
}
});
AnimatorSet animSet = new AnimatorSet()
.setDuration(getChangeDuration());
animSet.playTogether(heightAnim, alphaAnim);
animSet.addListener(new Animator.AnimatorListener() {
private boolean isCanceled;
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) {
if(!vh.isExpanded() && !isCanceled) {
expandableView.setVisibility(View.GONE);
}
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
@Override
public void onAnimationCancel(Animator animation) {
isCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animation) { }
});
AnimatorState animatorState = animatorMap.get(newHolder);
if(animatorState != null) {
animatorState.animSet.cancel();
// animation already running. Set start current play time of
// new animations to keep them smooth for reverse animation
alphaAnim.setCurrentPlayTime(animatorState.alphaAnim.getCurrentPlayTime());
heightAnim.setCurrentPlayTime(animatorState.heightAnim.getCurrentPlayTime());
animatorMap.remove(newHolder);
}
animatorMap.put(newHolder, new AnimatorState(alphaAnim, heightAnim, animSet));
dispatchChangeStarting(newHolder, false);
animSet.start();
return false;
}
public static class AnimatorState {
final ValueAnimator alphaAnim, heightAnim;
final AnimatorSet animSet;
public AnimatorState(ValueAnimator alphaAnim, ValueAnimator heightAnim, AnimatorSet animSet) {
this.alphaAnim = alphaAnim;
this.heightAnim = heightAnim;
this.animSet = animSet;
}
}
}
Questo è il risultato usando un leggermente modificato RecyclerView
demo.
Aggiornamento:
appena notato il tuo caso d'uso è in realtà un po 'diverso dopo aver riletto la domanda. Hai una visualizzazione di testo e vuoi solo mostrare una singola riga e poi espanderla per mostrare tutte le linee. Per fortuna che semplifica l'animatore personalizzato:
public static class MyAnimator extends DefaultItemAnimator {
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, ValueAnimator> animatorMap = new HashMap<>();
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
ValueAnimator prevAnim = animatorMap.get(newHolder);
if(prevAnim != null) {
prevAnim.reverse();
return false;
}
final ValueAnimator heightAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final TextView tv = vh.getExpandableTextView();
if(vh.isExpanded()) {
tv.measure(View.MeasureSpec.makeMeasureSpec(((View) tv.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
heightAnim = ValueAnimator.ofInt(tv.getHeight(), tv.getMeasuredHeight());
} else {
Paint.FontMetrics fm = tv.getPaint().getFontMetrics();
heightAnim = ValueAnimator.ofInt(tv.getHeight(), (int)(Math.abs(fm.top) + Math.abs(fm.bottom)));
}
heightAnim.setDuration(getChangeDuration());
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
tv.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
tv.requestLayout();
}
});
heightAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
});
animatorMap.put(newHolder, heightAnim);
dispatchChangeStarting(newHolder, false);
heightAnim.start();
return false;
}
}
E la nuova demo:
Ho aggiornato la mia risposta perché ho visto che non era proprio quello che stavi chiedendo. –
@George Mulligan funziona benissimo –
@eimmer hai risolto il problema. –