RecyclerView Q & A

Я создаю вопросы и ответы, где каждый вопрос является карточкой. Ответ начинается с первой строки, но когда его щелкнуть, он должен быть расширен, чтобы отобразить полный ответ.

Когда ответ расширяется / сворачивается, остальная часть RecyclerView должна анимировать, чтобы освободить место для расширения или свернуть, чтобы не показывать пробел.

Я смотрел разговор о анимации RecyclerView и считаю, что хочу создать собственный ItemAnimator, где я переопределяю animateChange. В этот момент я должен создать ObjectAnimator, чтобы оживить высоту LayoutParams. К сожалению, мне сложно связать все это вместе. Я также возвращаю true, когда переопределяет canReuseUpdatedViewHolder, поэтому мы повторно используем одного и того же пользователя.

@Override public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) { return true; } @Override public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { Log.d("test", "Run custom animation."); final ColorsAdapter.ColorViewHolder holder = (ColorsAdapter.ColorViewHolder) newHolder; FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.tvColor.getLayoutParams(); ObjectAnimator halfSize = ObjectAnimator.ofInt(holder.tvColor.getLayoutParams(), "height", params.height, 0); halfSize.start(); return super.animateChange(oldHolder, newHolder, preInfo, postInfo); } 

Прямо сейчас я просто пытаюсь что-то оживить, но ничего не происходит … Любые идеи?

Solutions Collecting From Web of "RecyclerView Q & A"

Я думаю, что ваша анимация не работает, потому что вы не можете анимировать LayoutParams таким образом, хотя это было бы аккуратно, если бы вы могли. Я попробовал код, который у вас был, и все, что он сделал, – это сделать мой взгляд на новую высоту. Только так, как я нашел, чтобы это работало, нужно было использовать ValueAnimator как вы можете видеть в приведенном ниже примере.

Я заметил некоторые недостатки при использовании DefaultItemAnimator чтобы показать / скрыть представление, обновив его видимость. Хотя это создало место для нового представления и анимировал остальные элементы вверх и вниз, основываясь на видимости расширяемого вида, я заметил, что он не оживлял высоту расширяемого вида. Он просто исчез на месте и неуместен, используя только альфа-значение.

Ниже представлен настраиваемый ItemAnimator с размерами и альфа-анимациями, основанный на скрытии / показе LinearLayout в макете ViewHolder . Он также позволяет повторно использовать один и тот же ViewHolder и правильно обрабатывать частичную анимацию, если пользователь быстро ViewHolder заголовок:

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

Это результат использования слегка модифицированной демонстрации RecyclerView .

Введите описание изображения здесь

Обновить:

Просто заметил, что ваш случай использования на самом деле немного отличается после перечитывания вопроса. У вас есть текстовое представление и вы хотите показать только одну строку, а затем расширить его, чтобы показать все строки. К счастью, это упрощает пользовательский аниматор:

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

И новая демонстрация:

Введите описание изображения здесь

Вам не нужно реализовывать пользовательский ItemAnimator по умолчанию DefaultItemAnimator уже поддерживает то, что вам нужно. Однако вам нужно рассказать об этом Animator, просмотр которого изменился. Я предполагаю, что вы вызываете notifyDataSetChanged() в своем адаптере. Это предотвращает анимацию для одного измененного элемента в RecyclerView (в вашем случае развернуть / свернуть элемент).

Вы должны использовать notifyItemChanged(int position) для элементов, которые были изменены. Вот короткий itemClicked(int position) который расширяет / сворачивает представления в RecyclerView. Поле expandPosition отслеживает текущий расширенный элемент:

 private void itemClicked(int position) { if (expandedPosition == -1) { // selected first item expandedPosition = position; notifyItemChanged(position); } else if (expandedPosition == position) { // collapse currently expanded item expandedPosition = -1; notifyItemChanged(position); } else { // collapse previously expanded item and expand new item int oldExpanded = expandedPosition; expandedPosition = position; notifyItemChanged(oldExpanded); notifyItemChanged(position); } } 

Это результат:

Введите описание изображения здесь

Согласно документации, вам нужно вернуть false в animateChange или вызвать runPendingAnimations позже. Попробуйте вернуть false.

http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemAnimator.html

Попробуйте этот класс:

 import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Paint; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; /** * Created by ankitagrawal on 2/14/16. */ public class AnimatedViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private int originalHeight = 0; private boolean mIsViewExpanded = false; private TextView textView; // ..... CODE ..... // public AnimatedViewHolder(View v) { super(v); v.setOnClickListener(this); // Initialize other views, like TextView, ImageView, etc. here // If isViewExpanded == false then set the visibility // of whatever will be in the expanded to GONE if (!mIsViewExpanded) { // Set Views to View.GONE and .setEnabled(false) textView.setLines(1); } } @Override public void onClick(final View view) { // Declare a ValueAnimator object ValueAnimator valueAnimator; if(mIsViewExpanded) { view.measure(View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED); mIsViewExpanded = false; valueAnimator = ValueAnimator.ofInt(view.getHeight(), view.getMeasuredHeight()); } else { Paint.FontMetrics fm = ((TextView)view).getPaint().getFontMetrics(); valueAnimator = ValueAnimator.ofInt(view.getHeight(), (int) (Math.abs(fm.top) + Math.abs(fm.bottom))); mIsViewExpanded = true; } valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.setDuration(200); valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { view.getLayoutParams().height = (Integer) animation.getAnimatedValue(); view.requestLayout(); } }); valueAnimator.start(); } } 

Преимущество этого подхода заключается в том, что он добавляет анимацию к событию onClick и наилучшим образом соответствует вашим требованиям.

Добавление анимации в зрителя будет слишком обременительным для вашего требования. И itemAnimator в соответствии с doc – это анимация для компоновки элементов, что также не подходит для вашего требования.

Для расширения и сглаживания анимации Android есть библиотека github. ExpandableRecyclerView

1). Добавить зависимости в файле build.gradle

 dependencies { compile 'com.android.support:recyclerview-v7:22.2.0' compile 'com.bignerdranch.android:expandablerecyclerview:1.0.3' } 

Изображение Expand & Collapse Animation

2) Развернуть и свернуть анимацию для анимации RecyclerView

 public static class ExampleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private int originalHeight = 0; private boolean isViewExpanded = false; private YourCustomView yourCustomView public ExampleViewHolder(View v) { super(v); v.setOnClickListener(this); // Initialize other views, like TextView, ImageView, etc. here // If isViewExpanded == false then set the visibility // of whatever will be in the expanded to GONE if (isViewExpanded == false) { // Set Views to View.GONE and .setEnabled(false) yourCustomView.setVisibility(View.GONE); yourCustomView.setEnabled(false); } } @Override public void onClick(final View view) { // If the originalHeight is 0 then find the height of the View being used // This would be the height of the cardview if (originalHeight == 0) { originalHeight = view.getHeight(); } // Declare a ValueAnimator object ValueAnimator valueAnimator; if (!mIsViewExpanded) { yourCustomView.setVisibility(View.VISIBLE); yourCustomView.setEnabled(true); mIsViewExpanded = true; valueAnimator = ValueAnimator.ofInt(originalHeight, originalHeight + (int) (originalHeight * 2.0)); // These values in this method can be changed to expand however much you like } else { mIsViewExpanded = false; valueAnimator = ValueAnimator.ofInt(originalHeight + (int) (originalHeight * 2.0), originalHeight); Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out a.setDuration(200); // Set a listener to the animation and configure onAnimationEnd a.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { yourCustomView.setVisibility(View.INVISIBLE); yourCustomView.setEnabled(false); } @Override public void onAnimationRepeat(Animation animation) { } }); // Set the animation on the custom view yourCustomView.startAnimation(a); } valueAnimator.setDuration(200); valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { Integer value = (Integer) animation.getAnimatedValue(); view.getLayoutParams().height = value.intValue(); view.requestLayout(); } }); valueAnimator.start(); } } 

Надеюсь, что это поможет вам.