Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 回收视图Q&;A._Android_Animation_Android Recyclerview - Fatal编程技术网

Android 回收视图Q&;A.

Android 回收视图Q&;A.,android,animation,android-recyclerview,Android,Animation,Android Recyclerview,我正在创建一个问答,每个问题都是一张卡片。答案开始显示第一行,但单击后应展开以显示完整答案 当答案展开/折叠时,RecyclerView的其余部分应设置动画,为展开或折叠腾出空间,以避免显示空白 我看了这个演讲,相信我想要一个定制的ItemAnimator,在这里我覆盖了animateChange。此时,我应该创建一个ObjectAnimator来设置视图LayoutParams高度的动画。不幸的是,我很难把这一切联系在一起。当重写canReuseUpdatedViewHolder时,我也返回t

我正在创建一个问答,每个问题都是一张卡片。答案开始显示第一行,但单击后应展开以显示完整答案

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

我看了这个演讲,相信我想要一个定制的ItemAnimator,在这里我覆盖了animateChange。此时,我应该创建一个ObjectAnimator来设置视图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);
}

现在我只是想做些动画,但什么也没发生。。。有什么想法吗?

根据文档,您需要在animateChange中返回false,或者稍后调用runPendingAnimations。尝试返回false


对于展开和折叠动画android,有一个github库。

1) .在
build.gradle
文件中添加依赖项

2) 展开并折叠RecyclerView动画的动画


希望这将对您有所帮助。

您不必实现自定义的
itemaniator
默认的
defaultitemaniator
已经支持您所需的内容。但是,您需要告诉这位动画师哪些视图已更改。我猜您正在适配器中调用
notifyDataSetChanged()
。这将防止在RecyclerView中为单个已更改的项目制作动画(在您的情况下,是项目的展开/折叠)

对于已更改的项目,应使用
notifyItemChanged(int位置)
。下面是一个简短的
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);
    }
}
结果是:


我认为您的动画不起作用,因为您无法以这种方式设置
LayoutParams
的动画,尽管如果可以的话,它会很整洁。我尝试了你的代码,它所做的只是让我的视图跳到了一个新的高度。我发现实现此功能的唯一方法是使用
ValueAnimator
,如下面的示例所示

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

下面是一个自定义的
itemaniator
,它基于在
ViewHolder
布局中隐藏/显示
LinearLayout
而具有大小和alpha动画。它还允许重复使用相同的
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;
        }
    }
}
还有新的演示:

试试这个课程:

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事件添加动画,并且最适合您的需求

将动画添加到viewholder对于您的要求来说太麻烦了。
根据文档,itemAnimator和itemAnimator是用于布局项目的动画,因此也不是最适合您的要求。

当android本机执行此操作时,您不需要外部库。根据NerdRanch提供的示例,这使用了两个独立的视图保持器。如果其他方法都不起作用的话,这似乎是一个绝对的最后手段。那么,你是如何真正地破坏视图的呢?您是否将其高度设置为0?将可见性设置为“已消失”?应用自定义动画?@eimmer每次调用
notifyItemChanged
,适配器中的方法
onBindViewHolder
都会被调用到相关位置。只需提供元素的扩展版本即可获得动画。gif中的项目视图只是
线性布局中的两个
文本视图
。折叠/扩展版本之间的区别是第二个
TextView
中的文本较长。这是一种简单而简洁的方法,但它确实存在一些缺点。在这种情况下,通过淡入淡出来交换视图保持架,并且更改的项目的实际大小更改不会设置动画。在本例中,这一点并不明显,因为您只是扩展了文本,但如果将文本完全替换为不同的内容,则文本将与旧视图和新视图保持架重叠。此外,由于高度没有设置动画,因此如果用户快速展开和折叠,会很尴尬。也许我在测试时做错了什么,但我写了一个定制的动画师来尝试解决下面的问题。我更新了我的答案,因为我发现它不完全符合你的要求。@George Mulligan工作正常great@eimmer“你们解决问题了吗?”乔治穆利根的回答是我看到的最好的。我正在从事一个github项目,提供您可以在这里找到的工作代码的完整示例。你看到我的答案了吗@eimmer,试试看,这也是一个很好的答案,顺便说一句,georgeMulligan也很好。我认为这是获得正确动画的最佳答案。注意:在循环使用viewholder时,跟踪viewholder中的动画可能会有问题。@eimmer您的注释是一个很好的观点,但我认为当有正在进行的动画时,
viewholder
将不会循环使用。Yigit在他的文章中提到,视图可以被
LayoutManager
删除,但要留在
RecyclerView
中,这样动画才能正常运行。我希望我能找到视频的源代码,这样我们就可以看到他们在做什么来反转动画了。@eimmer非常有趣的一点,因为我正试图实现这一点。但在看过Chet Haas的2015 Android Summit视频后,我发现您没有使用
recordPreLayoutInformation
recordPostLayoutInformation
在绑定VH a之前捕获值
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;
        }
    }
}
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;
    }
}
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();

    }
}