Android:展开/折叠动画

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

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

326512 次浏览

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

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

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;
}

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

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

是的,我同意上面的评论。事实上,它似乎是正确的(或者至少是最简单的?)事情要做的是指定(在XML中)一个初始布局高度“0px”——然后你可以传递另一个参数“toHeight”(即“最终高度”)到你的自定义动画子类的构造函数,例如,在上面的例子中,它看起来像这样:

    public DropDownAnim( View v, int toHeight ) { ... }

不管怎样,希望这能有所帮助!:)

我想在非常有用的以上回答中添加一些东西。如果你不知道你最终得到的高度,因为你的views .getHeight()返回0,你可以做下面的事情来获得高度:

contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
int finalHeight = view.getMeasuredHeight();

其中DUMMY_HIGH_DIMENSIONS是宽度/高度(以像素为单位),您的视图被限制为…当视图被ScrollView封装时,有这么大的数字是合理的。

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

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

至于坍塌:

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

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

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

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

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

这是我的解决方案。我认为这样更简单。它只是扩展了视图,但很容易扩展。

public class WidthExpandAnimation extends Animation
{
int _targetWidth;
View _view;


public WidthExpandAnimation(View view)
{
_view = view;
}


@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
if (interpolatedTime < 1.f)
{
int newWidth = (int) (_targetWidth * interpolatedTime);


_view.layout(_view.getLeft(), _view.getTop(),
_view.getLeft() + newWidth, _view.getBottom());
}
else
_view.requestLayout();
}


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


_targetWidth = width;
}


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

这对于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);
}
}));

我今天也遇到了同样的问题,我想这个问题的真正解决方案是这样的

<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
...
/>

你必须为所有涉及shift的顶层布局设置这个属性如果你现在将一个布局的可见性设置为GONE,另一个将在消失的布局释放它时占用空间。会有一个默认的动画,它是某种“淡出”,但我认为你可以改变这个-但最后一个我还没有测试,现在。


如果在RecyclerView项中使用此选项,则将视图的可见性设置为在onBindViewHolder中展开/折叠,并调用notifyItemChanged(位置)来触发转换。

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...


holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position)
}
}

如果你在onBindViewHolder中执行昂贵的操作,你可以使用notifyItemChanged(位置、载荷)优化部分更改

private const val UPDATE_LIST_VISIBILITY = 1


override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(UPDATE_LIST_VISIBILITY)) {
holder.list.visibility = data[position].listVisibility
} else {
onBindViewHolder(holder, position)
}
}


override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...


holder.list.visibility = data[position].listVisibility
holder.expandCollapse.setOnClickListener {
data[position].listVisibility = if (data[position].listVisibility == View.GONE) View.VISIBLE else View.GONE
notifyItemChanged(position, UPDATE_LIST_VISIBILITY)
}
}

如果你不想扩展或折叠所有的方式-这里是一个简单的HeightAnimation -

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;


public class HeightAnimation extends Animation {
protected final int originalHeight;
protected final View view;
protected float perValue;


public HeightAnimation(View view, int fromHeight, int toHeight) {
this.view = view;
this.originalHeight = fromHeight;
this.perValue = (toHeight - fromHeight);
}


@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
view.requestLayout();
}


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

用法:

HeightAnimation heightAnim = new HeightAnimation(view, view.getHeight(), viewPager.getHeight() - otherView.getHeight());
heightAnim.setDuration(1000);
view.startAnimation(heightAnim);

平滑动画请使用Handler运行方法.....享受展开/折叠动画

    class AnimUtils{


public void expand(final View v) {
int ANIMATION_DURATION=500;//in milisecond
v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
final int targtetHeight = v.getMeasuredHeight();


v.getLayoutParams().height = 0;
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)(targtetHeight * interpolatedTime);
v.requestLayout();
}


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


// 1dp/ms
a.setDuration(ANIMATION_DURATION);


// a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}






public 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;
}
};


// 1dp/ms
a.setDuration(ANIMATION_DURATION);
// a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
}

使用下面的代码调用:

       private void setAnimationOnView(final View inactive ) {
//I am applying expand and collapse on this TextView ...You can use your view


//for expand animation
new Handler().postDelayed(new Runnable() {
@Override
public void run() {


new AnimationUtililty().expand(inactive);


}
}, 1000);




//For collapse
new Handler().postDelayed(new Runnable() {
@Override
public void run() {


new AnimationUtililty().collapse(inactive);
//inactive.setVisibility(View.GONE);


}
}, 8000);


}

其他解决方案是:

               public void expandOrCollapse(final View v,String exp_or_colpse) {
TranslateAnimation anim = null;
if(exp_or_colpse.equals("expand"))
{
anim = new TranslateAnimation(0.0f, 0.0f, -v.getHeight(), 0.0f);
v.setVisibility(View.VISIBLE);
}
else{
anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, -v.getHeight());
AnimationListener collapselistener= new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}


@Override
public void onAnimationRepeat(Animation animation) {
}


@Override
public void onAnimationEnd(Animation animation) {
v.setVisibility(View.GONE);
}
};


anim.setAnimationListener(collapselistener);
}


// To Collapse
//


anim.setDuration(300);
anim.setInterpolator(new AccelerateInterpolator(0.5f));
v.startAnimation(anim);
}

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

在构造函数中还有一个小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);
}
}

这就是我的解决方案,我的ImageView100%增长到200%,并返回到他原来的大小,使用res/anim/文件夹中的两个动画文件

anim_grow.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>

anim_shrink.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="2.0"
android:toXScale="1.0"
android:fromYScale="2.0"
android:toYScale="1.0"
android:duration="3000"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="2000" />
</set>

发送一个ImageView到我的方法setAnimationGrowShrink()

ImageView img1 = (ImageView)findViewById(R.id.image1);
setAnimationGrowShrink(img1);

setAnimationGrowShrink()方法:

private void setAnimationGrowShrink(final ImageView imgV){
final Animation animationEnlarge = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_grow);
final Animation animationShrink = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_shrink);


imgV.startAnimation(animationEnlarge);


animationEnlarge.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}


@Override
public void onAnimationRepeat(Animation animation) {}


@Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationShrink);
}
});


animationShrink.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}


@Override
public void onAnimationRepeat(Animation animation) {}


@Override
public void onAnimationEnd(Animation animation) {
imgV.startAnimation(animationEnlarge);
}
});


}

我认为最简单的解决方案是将android:animateLayoutChanges="true"设置为你的LinearLayout,然后通过设置其可见性来显示/隐藏视图。工作就像一个魅力,但你无法控制动画持续时间

public static void expand(final View v, int duration, int targetHeight) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 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) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 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();
}

@Tom Esterez的回答,但更新为正确使用view.measure()每个Android getMeasuredHeight返回错误的值!

    // http://easings.net/
Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);


public static Animation expand(final View view) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = view.getMeasuredHeight();


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


Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {


view.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);


view.requestLayout();
}


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


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


return animation;
}


public static Animation collapse(final View view) {
final int initialHeight = view.getMeasuredHeight();


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


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


a.setInterpolator(easeInOutQuart);


int durationMillis = computeDurationFromHeight(view);
a.setDuration(durationMillis);


view.startAnimation(a);


return a;
}


private static int computeDurationFromHeight(View view) {
// 1dp/ms * multiplier
return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density);
}

我采取了@LenaYan的解决方案,不能正常工作 to me (因为它在折叠和/或展开之前将视图转换为0高度视图)并做了一些更改。< / p >

现在效果很好,通过获取视图的以前的 高度并开始扩展这个大小。坍缩也是一样的。

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

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提供的初始代码!

使用ValueAnimator:

ValueAnimator expandAnimation = ValueAnimator.ofInt(mainView.getHeight(), 400);
expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animation) {
int height = (Integer) animation.getAnimatedValue();
RelativeLayout.LayoutParams lp = (LayoutParams) mainView.getLayoutParams();
lp.height = height;
}
});




expandAnimation.setDuration(500);
expandAnimation.start();

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

    @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();
}

你可以稍微改变一下使用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 AccelerateDecelerateInterpolator())

根据@Tom Esterez和@Seth Nelson的解决方案,我对它们进行了简化。与原始解决方案一样,它不依赖于开发人员选项(动画设置)。

private void resizeWithAnimation(final View view, int duration, final int targetHeight) {
final int initialHeight = view.getMeasuredHeight();
final int distance = targetHeight - initialHeight;


view.setVisibility(View.VISIBLE);


Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1 && targetHeight == 0) {
view.setVisibility(View.GONE);
}
view.getLayoutParams().height = (int) (initialHeight + distance * interpolatedTime);
view.requestLayout();
}


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


a.setDuration(duration);
view.startAnimation(a);
}
public static void slide(View v, int speed, int pos) {
v.animate().setDuration(speed);
v.animate().translationY(pos);
v.animate().start();
}


// slide down
slide(yourView, 250, yourViewHeight);
// slide up
slide(yourView, 250, 0);
/**
* Animation that either expands or collapses a view by sliding it down to make
* it visible. Or by sliding it up so it will hide. It will look like it slides
* behind the view above.
*
*/
public class FinalExpandCollapseAnimation extends Animation
{
private View mAnimatedView;
private int mEndHeight;
private int mType;
public final static int COLLAPSE = 1;
public final static int EXPAND = 0;
private LinearLayout.LayoutParams mLayoutParams;
private RelativeLayout.LayoutParams mLayoutParamsRel;
private String layout;
private Context context;


/**
* Initializes expand collapse animation, has two types, collapse (1) and
* expand (0).
*
* @param view
*            The view to animate
* @param type
*            The type of animation: 0 will expand from gone and 0 size to
*            visible and layout size defined in xml. 1 will collapse view
*            and set to gone
*/
public FinalExpandCollapseAnimation(View view, int type, int height, String layout, Context context)
{
this.layout = layout;
this.context = context;
mAnimatedView = view;
mEndHeight = mAnimatedView.getMeasuredHeight();
if (layout.equalsIgnoreCase("linear"))
mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
else
mLayoutParamsRel = ((RelativeLayout.LayoutParams) view.getLayoutParams());
mType = type;
if (mType == EXPAND)
{
AppConstant.ANIMATED_VIEW_HEIGHT = height;
}
else
{
if (layout.equalsIgnoreCase("linear"))
mLayoutParams.topMargin = 0;
else
mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
}
setDuration(600);
}


@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f)
{
if (mType == EXPAND)
{
if (layout.equalsIgnoreCase("linear"))
{
mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT
+ (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
}
else
{
mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT
+ (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
}
mAnimatedView.setVisibility(View.VISIBLE);
}
else
{
if (layout.equalsIgnoreCase("linear"))
mLayoutParams.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
else
mLayoutParamsRel.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
}
mAnimatedView.requestLayout();
}
else
{
if (mType == EXPAND)
{
if (layout.equalsIgnoreCase("linear"))
{
mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT;
mLayoutParams.topMargin = 0;
}
else
{
mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT;
mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
}
mAnimatedView.setVisibility(View.VISIBLE);
mAnimatedView.requestLayout();
}
else
{
if (layout.equalsIgnoreCase("linear"))
mLayoutParams.height = 0;
else
mLayoutParamsRel.height = 0;
mAnimatedView.setVisibility(View.GONE);
mAnimatedView.requestLayout();
}
}
}


private int convertPixelsIntoDensityPixels(int pixels)
{
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) metrics.density * pixels;
}
}

该类可以通过以下方式调用

   if (findViewById(R.id.ll_specailoffer_show_hide).getVisibility() == View.VISIBLE) {
((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown_up);


FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
findViewById(R.id.ll_specailoffer_show_hide),
FinalExpandCollapseAnimation.COLLAPSE,
SpecialOfferHeight, "linear", this);
findViewById(R.id.ll_specailoffer_show_hide)
.startAnimation(finalExpandCollapseAnimation);
((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
} else {
((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown);


FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
findViewById(R.id.ll_specailoffer_show_hide),
FinalExpandCollapseAnimation.EXPAND,
SpecialOfferHeight, "linear", this);
findViewById(R.id.ll_specailoffer_show_hide)
.startAnimation(finalExpandCollapseAnimation);
((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
}

加上Tom Esterez的优秀的答案和Erik B的优秀的更新,我想我应该发布我自己的方法,将expand和contract方法压缩为一个。通过这种方式,你可以有这样一个动作……

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


}

来自@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);

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

Exapnd:

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是包含该视图的容器视图。

我改编了汤姆·埃斯特兹目前公认的答案,它工作,但有一个起伏和不太流畅的动画。我的解决方案基本上是用ValueAnimator代替Animation,它可以与你选择的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 );collapse( myView );

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

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

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

利用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()
}

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

enter image description here

最简单的一个是使用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上的一个,你可以检查他们在那里

下面是两个简单的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)
}
你不需要实现动画, 你可以放android:animateLayoutChanges="true" 以便每当子视图布局发生变化时, 它将被动画化…