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

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


当前回答

你的思路是对的。确保在动画开始之前将v1设置为布局高度为0。在开始动画之前,您需要初始化您的设置,使其看起来像动画的第一帧。

其他回答

这是一个合适的工作解决方案,我已经测试过了:

例如:

private void expand(View v) {
    v.setVisibility(View.VISIBLE);

    v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

    final int targetHeight = v.getMeasuredHeight();

    mAnimator = slideAnimator(0, targetHeight);
    mAnimator.setDuration(800);
    mAnimator.start();
}

崩溃:

private void collapse(View v) {
    int finalHeight = v.getHeight();

    mAnimator = slideAnimator(finalHeight, 0);

    mAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            //Height=0, but it set visibility to GONE
            llDescp.setVisibility(View.GONE);
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });
    mAnimator.start();
}

动画师的值:

private ValueAnimator slideAnimator(int start, int end) {
    ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);

    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            //Update Height
            int value = (Integer) valueAnimator.getAnimatedValue();
            ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
            layoutParams.height = value;
            v.setLayoutParams(layoutParams);
        }
    });
    return mAnimator;
}

视图v是要被动画的视图,PARENT_VIEW是包含该视图的容器视图。

你可以使用Transition或Animator来改变要展开/折叠的部分的可见性,或者使用不同布局的ConstraintSet。

最简单的一个是使用motionLayout与2个不同的布局和constraintSets改变从一个布局到另一个按钮点击。可以在布局之间进行更改

val constraintSet = ConstraintSet()
constraintSet.clone(this, R.layout.layout_collapsed)

val transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator(1.0f)
transition.setDuration(300)

TransitionManager.beginDelayedTransition(YOUR_VIEW, transition)
constraintSet.applyTo(YOUR_VIEW)

使用过渡api

RotateX.kt

我在gif中使用改变rotationX的过渡api创建了一个。

class RotateX : Transition {

    @Keep
    constructor() : super()

    @Keep
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun getTransitionProperties(): Array<String> {
        return TRANSITION_PROPERTIES
    }

    override fun captureStartValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun captureEndValues(transitionValues: TransitionValues) {
        captureValues(transitionValues)
    }

    override fun createAnimator(
        sceneRoot: ViewGroup,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {

        if (startValues == null || endValues == null) return null

        val startRotation = startValues.values[PROP_ROTATION] as Float
        val endRotation = endValues.values[PROP_ROTATION] as Float
        if (startRotation == endRotation) return null

        val view = endValues.view
        // ensure the pivot is set
        view.pivotX = view.width / 2f
        view.pivotY = view.height / 2f
        return ObjectAnimator.ofFloat(view, View.ROTATION_X, startRotation, endRotation)
    }

    private fun captureValues(transitionValues: TransitionValues) {
        val view = transitionValues.view
        if (view == null || view.width <= 0 || view.height <= 0) return
        transitionValues.values[PROP_ROTATION] = view.rotationX
    }

    companion object {

        private const val PROP_ROTATION = "iosched:rotate:rotation"
        private val TRANSITION_PROPERTIES = arrayOf(PROP_ROTATION)
    }
}

创建目标扩展按钮的XML文件

    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:interpolator/fast_out_slow_in">
    
        <transition class="com.smarttoolfactory.tutorial3_1transitions.transition.RotateX">
            <targets>
                <target android:targetId="@id/ivExpand" />
            </targets>
        </transition>
    
        <autoTransition android:duration="200" />
    
    </transitionSet>

My layout to be expanded or collapsed


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="4dp"
        android:layout_marginVertical="2dp"
        android:clickable="true"
        android:focusable="true"
        android:transitionName="@string/transition_card_view"
        app:cardCornerRadius="0dp"
        app:cardElevation="0dp"
        app:cardPreventCornerOverlap="false">


        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="16dp"
            android:paddingBottom="16dp">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/ivAvatar"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="8dp"
                android:scaleType="centerCrop"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:src="@drawable/avatar_1_raster" />

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/ivExpand"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:padding="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_baseline_expand_more_24" />

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:layout_marginTop="6dp"
                android:text="Some Title"
                android:textSize="20sp"
                android:textStyle="bold"
                app:layout_constraintStart_toEndOf="@+id/ivAvatar"
                app:layout_constraintTop_toTopOf="parent" />


            <TextView
                android:id="@+id/tvDate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:textColor="?android:textColorSecondary"
                android:textSize="12sp"
                app:layout_constraintStart_toStartOf="@+id/tvTitle"
                app:layout_constraintTop_toBottomOf="@id/tvTitle"
                tools:text="Tuesday 7pm" />

            <TextView
                android:id="@+id/tvBody"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="16dp"
                android:ellipsize="end"
                android:lines="1"
                android:text="@string/bacon_ipsum_short"
                android:textSize="16sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="@+id/ivAvatar"
                app:layout_constraintTop_toBottomOf="@id/tvDate" />


            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:orientation="horizontal"
                android:overScrollMode="never"
                android:visibility="gone"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tvBody"
                tools:listitem="@layout/item_image_destination" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </com.google.android.material.card.MaterialCardView>

</layout>

并设置项目的可见性,以折叠或展开

private fun setUpExpandedStatus() {
    if (isExpanded) {
        binding.recyclerView.visibility = View.VISIBLE
        binding.ivExpand.rotationX = 180f
    } else {
        binding.recyclerView.visibility = View.GONE
        binding.ivExpand.rotationX = 0f
    }
}

然后开始过渡

   val transition = TransitionInflater.from(itemView.context)
            .inflateTransition(R.transition.icon_expand_toggle)

  TransitionManager.beginDelayedTransition(parent, transition)

  isExpanded = !isExpanded
        
  setUpExpandedStatus()

我创建了动画和过渡样本,包括gif上的那个,你可以在那里检查它们。

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

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的速度

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

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

代码应该根据目标大小进行扩展或收缩。如果你想要一个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;
}

}