假设我有一个垂直线性布局:
[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有什么简单的方法吗?
我看到这个问题很受欢迎,所以我发布了我的实际解决方案。主要的优点是你不需要知道扩展的高度来应用动画,一旦视图被扩展,它会在内容改变时适应高度。这对我来说很有效。
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的速度
@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);
}
/**
* 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)目前接受的答案,这个答案有效,但动画起伏不定,不太流畅。我的解决方案基本上是用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);。
我今天也遇到了同样的问题,我想这个问题的真正解决方案是这样的
<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
...
/>
您必须为所有涉及移位的最顶层布局设置此属性。如果你现在将一个布局的可见性设置为GONE,另一个将在消失的布局释放它时占据空间。会有一个默认的动画,这是某种“淡出”,但我认为你可以改变这个-但最后一个我没有测试,现在。
如果在RecyclerView项目中使用这个,在onBindViewHolder中设置视图的可见性来展开/折叠,并调用notifyItemChanged(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)
}
}
如果你在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)
}
}