Подтвердить что ты не робот

RecyclerView Q & A

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

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

Я смотрел рассказ о анимациях RecyclerView и считаю, что мне нужен пользовательский ItemAnimator, где я переопределяю animateChange. В этот момент я должен создать ObjectAnimator для анимации высоты View 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);
}

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

4b9b3361

Ответ 1

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

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

Ниже представлен пользовательский ItemAnimator, который имеет размер и альфа-анимацию на основе скрытия/отображения LinearLayout в макете 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.

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

Update:

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 4

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

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 - это анимация для компоновки элементов, что также не соответствует вашим требованиям.

Ответ 5

Для развернуть и свернуть анимацию android есть для нее библиотека github. ExpandableRecyclerView

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

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

Изображение развернуть и свернуть анимацию

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

}

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