无法实施"径向反应"材料设计规范中的材料设计模式使用卡片和子类化RecyclerView

时间:2015-04-26 23:15:41

标签: android animation android-animation material-design android-recyclerview

The "Radial reaction" Material Design pattern can be found here.

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

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

网站上的

Here is an imgur album with a few stills from the clip可以更好地了解它们是如何实现的。

以下是Github上代码的链接:github / halfjew22 / AutoFitGridRecyclerView / tree / master / app / src / main / java / com / lustig / autofitgridrecyclerview (对不起,没有至少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);
        }
    }
}   

1 个答案:

答案 0 :(得分:0)

您不需要这些解决方法。

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

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