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

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


当前回答

我试图做一个我认为非常相似的动画,并找到了一个优雅的解决方案。这段代码假设你总是从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;
    }
}

其他回答

展开/折叠视图的最佳解决方案:

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        View view = buttonView.getId() == R.id.tb_search ? fSearch : layoutSettings;
        transform(view, 200, isChecked
            ? ViewGroup.LayoutParams.WRAP_CONTENT
            : 0);
    }

    public static void transform(final View v, int duration, int targetHeight) {
        int prevHeight  = v.getHeight();
        v.setVisibility(View.VISIBLE);
        ValueAnimator animator;
        if (targetHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
            v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            animator = ValueAnimator.ofInt(prevHeight, v.getMeasuredHeight());
        } else {
            animator = ValueAnimator.ofInt(prevHeight, targetHeight);
        }
        animator.addUpdateListener(animation -> {
            v.getLayoutParams().height = (animation.getAnimatedFraction() == 1.0f)
                    ? targetHeight
                    : (int) animation.getAnimatedValue();
            v.requestLayout();
        });
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(duration);
        animator.start();
    }

来自@Tom Esterez和@Geraldo Neto的组合解决方案

public static void expandOrCollapseView(View v,boolean expand){

    if(expand){
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        final int targetHeight = v.getMeasuredHeight();
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(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(500);
        valueAnimator.start();
    }
    else
    {
        final int initialHeight = v.getMeasuredHeight();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(initialHeight,0);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
                if((int)animation.getAnimatedValue() == 0)
                    v.setVisibility(View.GONE);
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(500);
        valueAnimator.start();
    }
}

//sample usage
expandOrCollapseView((Your ViewGroup),(Your ViewGroup).getVisibility()!=View.VISIBLE);

利用Kotlin扩展函数这是测试和最短的答案

在任何视图上调用animateVisibility(expand/collapse)即可。

fun View.animateVisibility(setVisible: Boolean) {
    if (setVisible) expand(this) else collapse(this)
}

private fun expand(view: View) {
    view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    val initialHeight = 0
    val targetHeight = view.measuredHeight

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

    animateView(view, initialHeight, targetHeight)
}

private fun collapse(view: View) {
    val initialHeight = view.measuredHeight
    val targetHeight = 0

    animateView(view, initialHeight, targetHeight)
}

private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
    val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
    valueAnimator.addUpdateListener { animation ->
        v.layoutParams.height = animation.animatedValue as Int
        v.requestLayout()
    }
    valueAnimator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationEnd(animation: Animator) {
            v.layoutParams.height = targetHeight
        }

        override fun onAnimationStart(animation: Animator) {}
        override fun onAnimationCancel(animation: Animator) {}
        override fun onAnimationRepeat(animation: Animator) {}
    })
    valueAnimator.duration = 300
    valueAnimator.interpolator = DecelerateInterpolator()
    valueAnimator.start()
}

我创建的版本中,你不需要指定布局高度,因此它更容易和更干净的使用。解决方案是在动画的第一帧中获取高度(至少在我的测试中是可用的)。通过这种方式,您可以为视图提供任意高度和底边距。

在构造函数中还有一个小hack -底部边距被设置为-10000,以便视图在转换之前保持隐藏(防止闪烁)。

public class ExpandAnimation extends Animation {


    private View mAnimatedView;
    private ViewGroup.MarginLayoutParams mViewLayoutParams;
    private int mMarginStart, mMarginEnd;

    public ExpandAnimation(View view) {
        mAnimatedView = view;
        mViewLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        mMarginEnd = mViewLayoutParams.bottomMargin;
        mMarginStart = -10000; //hide before viewing by settings very high negative bottom margin (hack, but works nicely)
        mViewLayoutParams.bottomMargin = mMarginStart;
        mAnimatedView.setLayoutParams(mViewLayoutParams);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
            //view height is already known when the animation starts
            if(interpolatedTime==0){
                mMarginStart = -mAnimatedView.getHeight();
            }
            mViewLayoutParams.bottomMargin = (int)((mMarginEnd-mMarginStart) * interpolatedTime)+mMarginStart;
            mAnimatedView.setLayoutParams(mViewLayoutParams);
    }
}

这是非常简单的droidQuery。首先,考虑这样的布局:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <LinearLayout
        android:id="@+id/v1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 1" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/v2"
        android:layout_width="wrap_content"
        android:layout_height="0dp" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 2" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 3" />
    </LinearLayout>
</LinearLayout>

我们可以使用下面的代码将高度动画到所需的值-比如100dp:

//convert 100dp to pixel value
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());

然后使用droidQuery进行动画。最简单的方法是:

$.animate("{ height: " + height + "}", new AnimationOptions());

为了让动画更吸引人,可以考虑添加一个easing:

$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE));

你也可以使用duration()方法改变AnimationOptions上的持续时间,或者处理动画结束时发生的事情。对于一个复杂的例子,试试:

$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE)
                                                             .duration(1000)
                                                             .complete(new Function() {
                                                                 @Override
                                                                 public void invoke($ d, Object... args) {
                                                                     $.toast(context, "finished", Toast.LENGTH_SHORT);
                                                                 }
                                                             }));