假设我有一个垂直线性布局:

[v1]
[v2]

默认情况下,v1已经可见= GONE。我想用一个展开动画显示v1,同时下推v2。

我是这样做的:

Animation a = new Animation()
{
    int initialHeight;

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final int newHeight = (int)(initialHeight * interpolatedTime);
        v.getLayoutParams().height = newHeight;
        v.requestLayout();
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        initialHeight = height;
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
};

但是用这个解决方案,当动画开始时我有一个眨眼。我认为这是由于v1在应用动画之前显示全尺寸。

用javascript,这是一行jQuery!android有什么简单的方法吗?


当前回答

我改编了汤姆·埃斯特兹(Tom Esterez)目前接受的答案,这个答案有效,但动画起伏不定,不太流畅。我的解决方案基本上是用ValueAnimator替换动画,它可以与你选择的Interpolator相匹配,以实现各种效果,如超调、反弹、加速等。

这个解决方案非常适用于具有动态高度的视图(即使用WRAP_CONTENT),因为它首先测量实际所需的高度,然后动画到该高度。

public static void expand(final View v) {
    v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    final int targetHeight = v.getMeasuredHeight();

    // Older versions of android (pre API 21) cancel animations for views with a height of 0.
    v.getLayoutParams().height = 1;
    v.setVisibility(View.VISIBLE);

    ValueAnimator va = ValueAnimator.ofInt(1, targetHeight);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    va.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
            v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }

        @Override public void onAnimationStart(Animator animation) {}
        @Override public void onAnimationCancel(Animator animation) {}
        @Override public void onAnimationRepeat(Animator animation) {}
    });
    va.setDuration(300);
    va.setInterpolator(new OvershootInterpolator());
    va.start();
}

public static void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    ValueAnimator va = ValueAnimator.ofInt(initialHeight, 0);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    va.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
            v.setVisibility(View.GONE);
        }

        @Override public void onAnimationStart(Animator animation) {}
        @Override public void onAnimationCancel(Animator animation) {}
        @Override public void onAnimationRepeat(Animator animation) {}
    });
    va.setDuration(300);
    va.setInterpolator(new DecelerateInterpolator());
    va.start();
}

然后你只需调用expand(myView);或崩溃(myView);。

其他回答

加上Tom Esterez的精彩回答和Erik B的精彩更新,我想我应该发布我自己的观点,将扩展和收缩方法压缩为一个。通过这种方式,你可以有这样一个动作……

button.setOnClickListener(v -> expandCollapse(view));

... 它调用下面的方法,并让它在每次onClick()之后弄清楚要做什么…

public static void expandCollapse(View view) {

    boolean expand = view.getVisibility() == View.GONE;
    Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);

    view.measure(
        View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    );

    int height = view.getMeasuredHeight();
    int duration = (int) (height/view.getContext().getResources().getDisplayMetrics().density);

    Animation animation = new Animation() {
        @Override protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (expand) {
                view.getLayoutParams().height = 1;
                view.setVisibility(View.VISIBLE);
                if (interpolatedTime == 1) {
                    view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                } else {
                    view.getLayoutParams().height = (int) (height * interpolatedTime);
                }
                view.requestLayout();
            } else {
                if (interpolatedTime == 1) {
                    view.setVisibility(View.GONE);
                } else {
                    view.getLayoutParams().height = height - (int) (height * interpolatedTime);
                    view.requestLayout();
                }
            }
        }
        @Override public boolean willChangeBounds() {
            return true;
        }
    };

    animation.setInterpolator(easeInOutQuart);
    animation.setDuration(duration);
    view.startAnimation(animation);

}

这是一个片段,我用来调整一个视图(线性布局)的宽度与动画。

代码应该根据目标大小进行扩展或收缩。如果你想要一个fill_parent宽度,你必须在将标志设置为true时将父类. getmeasuredwidth作为目标宽度。

希望它能帮助到你们中的一些人。

public class WidthResizeAnimation extends Animation {
int targetWidth;
int originaltWidth;
View view;
boolean expand;
int newWidth = 0;
boolean fillParent;

public WidthResizeAnimation(View view, int targetWidth, boolean fillParent) {
    this.view = view;
    this.originaltWidth = this.view.getMeasuredWidth();
    this.targetWidth = targetWidth;
    newWidth = originaltWidth;
    if (originaltWidth > targetWidth) {
        expand = false;
    } else {
        expand = true;
    }
    this.fillParent = fillParent;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    if (expand && newWidth < targetWidth) {
        newWidth = (int) (newWidth + (targetWidth - newWidth) * interpolatedTime);
    }

    if (!expand && newWidth > targetWidth) {
        newWidth = (int) (newWidth - (newWidth - targetWidth) * interpolatedTime);
    }
    if (fillParent && interpolatedTime == 1.0) {
        view.getLayoutParams().width = -1;

    } else {
        view.getLayoutParams().width = newWidth;
    }
    view.requestLayout();
}

@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    super.initialize(width, height, parentWidth, parentHeight);
}

@Override
public boolean willChangeBounds() {
    return true;
}

}

另一种选择是使用以下缩放因子的缩放动画进行扩展:

ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1);

至于坍塌:

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);

我已经使用了相同的代码块,已经在接受的答案,但它不会像在android 9一样工作,所以更新措施根据这一点

v.measure(MeasureSpec.makeMeasureSpec(parentView.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(parentView.getWidth(), MeasureSpec.AT_MOST));

约束的工作方式在android 9中有所不同。

好吧,我刚刚发现了一个非常丑陋的解决方案:

public static Animation expand(final View v, Runnable onEnd) {
    try {
        Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
        m.setAccessible(true);
        m.invoke(
            v,
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
            MeasureSpec.makeMeasureSpec(((View)v.getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST)
        );
    } catch (Exception e){
        Log.e("test", "", e);
    }
    final int initialHeight = v.getMeasuredHeight();
    Log.d("test", "initialHeight="+initialHeight);

    v.getLayoutParams().height = 0;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final int newHeight = (int)(initialHeight * interpolatedTime);
            v.getLayoutParams().height = newHeight;
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };
    a.setDuration(5000);
    v.startAnimation(a);
    return a;
}

请随意提出一个更好的解决方案!