使用卡片和子类化回收器视图实现材料设计规范中的"Radial reaction"材料设计模式时遇到问题



"径向反应"材料设计模式可在此处找到。

现在,我正在使用RecyclerView的子类实现,根据设备的大小自动提供正确数量的卡片行和列。在RecyclerView的onBindViewHolder的实现中,我将布局的Card元素添加到一个自定义的AnimationHelper类中,该类反过来应该在轮到该Card时为该Card设置动画。我现在的问题是决定什么时候轮到每个人。。。

我的动画效果有点,但我需要一些帮助来弄清楚如何更准确地表示模式。现在,我有一个快速的"多米诺骨牌"效应,它看起来不太对劲。在进一步检查材料文档中的剪辑后,很明显,第一个单元格先着色,然后是下面和右边的单元格,继续使用该图案,直到所有六个单元格都变暗。这就是我需要帮助的地方,无论卡片有多少行和多少列,我都需要一种方法来动画化我的卡片,以实现这种难以捉摸的视觉设计模式。

这是一张imgur专辑,其中有一些来自网站上剪辑的剧照,以更好地了解他们是如何达到效果的。

以下是Github上代码的链接:Github/harfjew22/AutoFitGridRecyclerView/tree/master/app/src/main/java.com/lustig/autofitgridcyclerview(对不起,没有至少10个代表,我不能发布链接)

任何对我的代码的帮助、帮助、建设性的批评,以及关于更好地实现这部动画的建议,都是非常受欢迎的。

以下是相关的代码片段:

动画助手

package com.lustig.autofitgridrecyclerview.animations;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.os.Handler;
import android.view.View;
import java.util.ArrayList;
/**
* With this class, I am going to achieve the radial reaction effect     described on the
*  Material Guidelines docs here:
*/
/**
* Questions / Concerns
*
* How many animations can happen at one time?
* How should I deal with that?
* Will a simple boolean flag work for 2, 3 or more animations occurring in parallel?
*/
/**
* I'm not worried about efficiency so much as actually doing what I want to do.
* After I get something working, I can work on finding better ways to do things,
* but if I bog myself down with trying to pre-optimize, ain't shit gon' get did...
*/
public class AnimationHelper {
public static final long DEFAULT_DURATION_MS = 200;
private static final String DEFAULT_PROPERTY_TO_ANIMATE = "alpha";
/* Type of property of View being animated */
private String mViewPropertyToAnimate = DEFAULT_PROPERTY_TO_ANIMATE;
/* Duration of animations, set to default value, but can be changed */
private long mAnimationDuration = DEFAULT_DURATION_MS;
/* Value to determine if an animation is currently occurring */
private boolean isCurrentlyAnimating = false;
/* First, I need an animation queue. I should use a generic view, I'll try that first */
private ArrayList<View> mViewsToAnimate = new ArrayList<View>();
/* Next I'll need a method to add to the animation queue */
public void addViewToQueue(View viewToAnimate) {
/**
* If I've already animated the view, don't do it again
*/
if (viewToAnimate.getVisibility() == View.VISIBLE) {
return;
}
mViewsToAnimate.add(viewToAnimate);
/* This method is the meat and potatoes of this class */
startAnimationChain();
}
/* This method will be in charge of starting the domino effect */
private void startAnimationChain() {
/* If there is currently not an animation happening, start one */
if (mViewsToAnimate.size() >= 2 && !isCurrentlyAnimating) {
animateSingleView(mViewsToAnimate.get(0));
/**
* If we are currently animating, just wait. The next view animation should
* automatically be spawned
*/
} else if (isCurrentlyAnimating) {
// Just wait, animations should continue until the list is empty
}
}
private void animateSingleView(final View v) {
/* Right now, using a single animation, will change in the future */
ObjectAnimator animator =
ObjectAnimator.ofFloat(v, mViewPropertyToAnimate, 0f, 1f);
animator.setDuration(mAnimationDuration);
animator.addListener(
new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
v.setVisibility(View.VISIBLE);
/* Remove the currently animating View from the list */
mViewsToAnimate.remove(v);
/* Notify the class Object that an animation is happening */
isCurrentlyAnimating = true;
/**
* If, after removing the currently animating view, the list is not empty,
* start the next animation after the current animation is halfway done.
*/
if (!mViewsToAnimate.isEmpty()) {
/* Set a timer for (mAnimationDuration / 2) ms to start next animation */
final Handler handler = new Handler();
handler.postDelayed(
new Runnable() {
@Override
public void run() {
// Animate the first item in the list because each time
// an animation starts, it is removed from the list
if (!mViewsToAnimate.isEmpty()) {
animateSingleView(mViewsToAnimate.get(0));
}
}
}, mAnimationDuration / 6);
}
}
@Override
public void onAnimationEnd(Animator animation) {
/**
* Setting this boolean flag could potentially cause issues as I'm going
* to have to use a Runnable to wait some time before starting the next
* animation. If there are any bugs, come back here, debug, and make sure
* that this flag is behaving as expected
*/
/* Notify the class Object that the current animation has finished */
isCurrentlyAnimating = false;
}
@Override
public void onAnimationCancel(Animator animation) {
// Ignored intentionally
}
@Override
public void onAnimationRepeat(Animator animation) {
// Ignored intentionally
}
});
animator.start();
}
}

~~~~~ AutofitRecyclerView

package com.lustig.autofitgridrecyclerview.recyclers;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
public class AutofitRecyclerView extends RecyclerView {
private GridLayoutManager manager;
private int columnWidth = -1;
public AutofitRecyclerView(Context context) {
super(context);
init(context, null);
}
public AutofitRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
int[] attrsArray = {
android.R.attr.columnWidth
};
TypedArray array = context.obtainStyledAttributes(attrs, attrsArray);
columnWidth = array.getDimensionPixelSize(0, -1);
array.recycle();
}
manager = new GridLayoutManager(getContext(), 1);
setLayoutManager(manager);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
if (columnWidth > 0) {
int spanCount = Math.max(1, getMeasuredWidth() / columnWidth);
manager.setSpanCount(spanCount);
}
}
}

适配器和ViewHolder的实现

package com.lustig.autofitgridrecyclerview.adapters;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.lustig.autofitgridrecyclerview.R;
import com.lustig.autofitgridrecyclerview.animations.AnimationHelper;
import java.util.ArrayList;
import java.util.List;
public class NumberedAdapter extends RecyclerView.Adapter<NumberedAdapter.TextViewHolder> {
private List<String> labels;
private AnimationHelper mHelper;
public NumberedAdapter(int count) {
mHelper = new AnimationHelper();
labels = new ArrayList<String>(count);
for (int i = 0; i < count; ++i) {
labels.add(String.valueOf(i));
}
}
@Override
public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new TextViewHolder(view);
}
@Override
public void onBindViewHolder(final TextViewHolder holder, final int position) {
final String label = labels.get(position);
holder.textView.setText(label);
mHelper.addViewToQueue(holder.cardView);
}
@Override
public int getItemCount() {
return labels.size();
}
public class TextViewHolder extends RecyclerView.ViewHolder {
public CardView cardView;
public TextView textView;
public TextViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.card);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
}   

您不需要这些变通方法。

实现自定义ItemAnimator。按下后退箭头时,从适配器和notifyItemRangeRemoved(0, mAdapter.getItemCount())中移除所有项目;这将调用ItemAnimator到animateRemove所有视图。在那里你可以记录意见,决定订单等;之后,您将收到runPendingAnimations调用,在那里您可以运行实际的动画。

有关详细信息,请参阅DefaultItemAnimator的实现。