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

[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有什么简单的方法吗?


当前回答

我采用了@LenaYan的解决方案,但它并没有正常工作 对我来说(因为它在折叠和/或扩展之前将视图转换为0高度视图)并做了一些更改。

现在它工作得很好,通过获取视图之前的高度并开始扩展这个大小。坍缩也是一样的。

你可以简单地复制和粘贴下面的代码:

public static void expand(final View v, int duration, int targetHeight) {

    int prevHeight  = v.getHeight();

    v.setVisibility(View.VISIBLE);
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

public static void collapse(final View v, int duration, int targetHeight) {
    int prevHeight  = v.getHeight();
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

用法:

//Expanding the View
   expand(yourView, 2000, 200);

// Collapsing the View     
   collapse(yourView, 2000, 100);

很容易!

感谢LenaYan提供的初始代码!

其他回答

你可以稍微改变一下使用ViewPropertyAnimator。若要折叠,请将视图缩放到1像素的高度,然后隐藏它。展开,显示它,然后展开到它的高度。

private void collapse(final View view) {
    view.setPivotY(0);
    view.animate().scaleY(1/view.getHeight()).setDuration(1000).withEndAction(new Runnable() {
        @Override public void run() {
            view.setVisibility(GONE);
        }
    });
}

private void expand(View view, int height) {
    float scaleFactor = height / view.getHeight();

    view.setVisibility(VISIBLE);
    view.setPivotY(0);
    view.animate().scaleY(scaleFactor).setDuration(1000);
}

枢轴告诉视图从哪里开始缩放,默认是在中间。持续时间是可选的(默认= 1000)。你也可以设置插值器来使用,比如.setInterpolator(new accelerateeraderateinterpolator ())

我看到这个问题很受欢迎,所以我发布了我的实际解决方案。主要的优点是你不需要知道扩展的高度来应用动画,一旦视图被扩展,它会在内容改变时适应高度。这对我来说很有效。

public static void expand(final View v) {
    int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
    int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
    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);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targetHeight * interpolatedTime);
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };
    
    // Expansion speed of 1dp/ms
    a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

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

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };
    
    // Collapse speed of 1dp/ms
    a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

正如@Jefferson在评论中提到的,你可以通过改变动画的持续时间(以及速度)来获得更流畅的动画。目前,它已设置为1dp/ms的速度

下面是两个简单的kotlin扩展函数概述。

fun View.expand() {
    visibility = View.VISIBLE
    val animate = TranslateAnimation(0f, 0f, -height.toFloat(), 0f)
    animate.duration = 200
    animate.fillAfter = true
    startAnimation(animate)
}

fun View.collapse() {
    val animate = TranslateAnimation(0f, 0f, 0f, -height.toFloat() )
    animate.duration = 200
    animate.fillAfter = true
    startAnimation(animate)
}

我试图做一个我认为非常相似的动画,并找到了一个优雅的解决方案。这段代码假设你总是从0->h或h->0 (h是最大高度)。三个构造函数参数是view =要动画的视图(在我的例子中是webview), targetHeight =视图的最大高度,以及down =一个布尔值,用于指定方向(true =展开,false =折叠)。

public class DropDownAnim extends Animation {
    private final int targetHeight;
    private final View view;
    private final boolean down;

    public DropDownAnim(View view, int targetHeight, boolean down) {
        this.view = view;
        this.targetHeight = targetHeight;
        this.down = down;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        int newHeight;
        if (down) {
            newHeight = (int) (targetHeight * interpolatedTime);
        } else {
            newHeight = (int) (targetHeight * (1 - interpolatedTime));
        }
        view.getLayoutParams().height = newHeight;
        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;
    }
}

加上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);

}