RecyclerView问答

eim*_*mer 19 animation android android-recyclerview

我正在创建一个问答,每个问题都是一张卡片.答案开始显示第一行,但是当点击它时,它应该展开以显示完整的答案.

当答案扩展/折叠时,RecyclerView的其余部分应设置动画以为扩展或折叠腾出空间,以避免显示空白区域.

我观看了有关RecyclerView动画的演讲,并且相信我想要一个自定义的ItemAnimator,我在其中覆盖animateChange.那时我应该创建一个ObjectAnimator来动画View的LayoutParams的高度.不幸的是,我很难将它们捆绑在一起.当覆盖canReuseUpdatedViewHolder时我也返回true,因此我们重用相同的viewholder.

@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);
}
Run Code Online (Sandbox Code Playgroud)

现在我只想尝试动画,但没有任何反应......任何想法?

Geo*_*gan 10

我认为你的动画不起作用,因为你不能以LayoutParams这种方式制作动画,尽管如果可以的话它会很好.我尝试了你所拥有的代码,它所做的就是让我的视图跳到新的高度.我发现让它工作的唯一方法是使用a ValueAnimator,如下面的示例所示.

当我使用DefaultItemAnimator通过更新其可见性来显示/隐藏视图时,我注意到了一些缺点.虽然它确实为新视图腾出了空间,并根据可扩展视图的可见性来上下动画其余项目,但我注意到它没有为可扩展视图的高度设置动画.它只是使用alpha值褪色到位并且不合适.

下面是一个定制的ItemAnimator基于隐藏/显示具有大小和α动画LinearLayoutViewHolder布局.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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是使用略微修改的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

而新的演示:

在此输入图像描述

  • 我认为这是使动画正确的最佳答案。注意:当 ViewHolder 被回收时,跟踪 viewholder 中的动画可能会出现问题。 (2认同)

And*_*ger 9

您不必实现自定义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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是结果:

在此输入图像描述