Android TranslateAnimation 在动画后重置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2650351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 06:46:21  来源:igfitidea点击:

Android TranslateAnimation resets after animation

androidanimation

提问by monmonja

I'm creating something like a SlideDrawer but with most customization, basically the thing is working but the animation is flickering at the end.

我正在创建类似 SlideDrawer 的东西,但大多数自定义功能基本上都在工作,但动画在最后闪烁。

To further explain, I got an TranslateAnimation then after this animation it returns back to the original position, if i set setFillAfter then the buttons inside the layout stops working. If i listen to onAnimationEnd and set other's layout to View.GONE the layout fickers. Judging from it is that on animation end, the view goes back to original position before the View.GONE is called.

为了进一步解释,我得到了一个 TranslateAnimation 然后在这个动画之后它返回到原始位置,如果我设置 setFillAfter 然后布局内的按钮停止工作。如果我听 onAnimationEnd 并将其他人的布局设置为 View.GONE 布局错误。由此判断,在动画结束时,视图返回到调用View.GONE之前的原始位置。

Any advice would be awesome. Thanks

任何建议都会很棒。谢谢

回答by Soham

Hereis the actual bug related to this issue

是与此问题相关的实际错误

This basically states that the onAnimationEnd(...)method doesn't really work well when an AnimationListener is attached to an Animation

这基本上表明onAnimationEnd(...)当 AnimationListener 附加到 Animation 时,该方法并不能很好地工作

The workaround is to listen for the animation events in the view to which you were applying the animation to For example if initially you were attaching the animation listener to the animation like this

解决方法是在您应用动画的视图中侦听动画事件例如,如果最初您将动画侦听器附加到这样的动画

mAnimation.setAnimationListener(new AnimationListener() {
    @Override
    public void onAnimationEnd(Animation arg0) {
                       //Functionality here
    }

and then applying to the animation to a ImageViewlike this

然后ImageView像这样应用到动画中

mImageView.startAnimation(mAnimation);

To work around this issue, you must now create a custom ImageView

要解决此问题,您现在必须创建自定义 ImageView

public Class myImageView extends ImageView {

and then override the onAnimationEnd method of the View class and provide all the functionality there

然后覆盖 View 类的 onAnimationEnd 方法并在那里提供所有功能

@Override
protected void onAnimationEnd() {
    super.onAnimationEnd();
    //Functionality here
}

This is the proper workaround for this issue, provide the functionality in the over-riden View -> onAnimationEnd(...)method as opposed to the onAnimationEnd(...)method of the AnimationListener attached to the Animation.

这是解决此问题的正确方法,在覆盖的 View ->onAnimationEnd(...)方法中提供功能,而不是onAnimationEnd(...)附加到动画的 AnimationListener 方法。

This works properly and there is no longer any flicker towards the end of the animation. Hope this helps

这工作正常,动画结束时不再有任何闪烁。希望这可以帮助

回答by jul

From API 11, you can use the ObjectAnimator, which actually changes the view properties, i.e. in the case of a translation, the view will remain at the position it reaches after the animation.

从 API 11 开始,您可以使用ObjectAnimator,它实际上会更改视图属性,即在平移的情况下,视图将保留在动画后到达的位置。

ObjectAnimator objectAnimator= ObjectAnimator.ofFloat(mContent_container, "translationX", startX, endX);
objectAnimator.setDuration(1000);
objectAnimator.start();

More here.

更多在这里

回答by jordanti

Soham's answer above works for me, although it's worth pointing out (since it wasn't immediately obvious to me when first reading this thread) that you can still get very nearly the same behavior as an animation listener by setting a separate listener on the view to be run at the end of your View's onAnimationStart()and onAnimationEnd().

Soham 上面的回答对我有用,尽管值得指出(因为在第一次阅读此线程时对我来说并不是很明显),您仍然可以通过在视图上设置单独的侦听器来获得与动画侦听器几乎相同的行为在您的视图onAnimationStart()onAnimationEnd().

For instance, if your code needs to disable a button for the duration of an animation:

例如,如果您的代码需要在动画持续时间内禁用按钮:

Animation a = getAnimation(/* your code */);
a.setDuration(1000);
a.setAnimationListener(new AnimationListener() {
   @Override
   public void onAnimationStart(Animation arg0) {
     myButton.setEnabled(false);
   }

   @Override
   public void onAnimationEnd(Animation arg0) {
     myButton.setEnabled(true);
   }
});
someView.startAnimation(a);

Currently, someViewdoesn't know about myButton, and I'd like to keep it that way. You can just create some listener on your custom view class that gets called in the same fashion:

目前,someView不知道myButton,我想保持这种状态。您可以在自定义视图类上创建一些以相同方式调用的侦听器:

public final class SomeView extends View {
    // other code

    public interface RealAnimationListener {
      public void onAnimationStart();
      public void onAnimationEnd();
    }

    private RealAnimationListener mRealAnimationListener;

    public void setRealAnimationListener(final RealAnimationListener listener) {
      mRealAnimationListener = listener;
    }

    @Override
    protected void onAnimationStart() {
      super.onAnimationStart();
      if (mRealAnimationListener != null) {
         mRealAnimationListener.onAnimationStart();
      }
    }

    @Override
    protected void onAnimationEnd() {
      super.onAnimationEnd();
      if (mRealAnimationListener != null) {
         mRealAnimationListener.onAnimationEnd();
      }
    }
}

And then back in your other code (probably an Activity):

然后回到你的其他代码(可能是一个活动):

Animation a = getAnimation(/* your code */);
a.setDuration(1000);
someView.setRealAnimationListener(new RealAnimationListener() {
   @Override
   public void onAnimationStart() {
     myButton.setEnabled(false);
   }

   @Override
   public void onAnimationEnd() {
     myButton.setEnabled(true);
   }
});
someView.startAnimation(a);

This way you keep your components separated cleanly while still getting an AnimationListener that works.

通过这种方式,您可以将组件干净地分开,同时仍然可以得到一个有效的 AnimationListener。

回答by jordanti

Using Soham's answer, here is an ImageView specific to fade animations:

使用 Soham 的回答,这是一个专门用于淡入淡出动画的 ImageView:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;

/*
 * Custom view to prevent flickering on animation end
 * 
 * http://stackoverflow.com/questions/2650351/android-translateanimation-resets-after-animation
 */
public class FadeableImageView extends ImageView {

public FadeableImageView(Context context) {
    super(context);
}

public FadeableImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public FadeableImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
protected void onAnimationEnd() {
    super.onAnimationEnd();
    this.setVisibility(View.GONE);
}
}

And here is my animation code:

这是我的动画代码:

protected void startSplash() {
    final FadeableImageView splash = (FadeableImageView) findViewById(R.id.splash);

    Animation fadeOut = new AlphaAnimation(1, 0);
    fadeOut.setDuration(2000);
    splash.startAnimation(fadeOut);
}

回答by CommonsWare

Get rid of setFillAfterand just use View.GONEin onAnimationEnd(). See herefor a sample custom View that implements a sliding panel using a TranslateAnimation.

摆脱setFillAfter并仅View.GONEonAnimationEnd(). 请参阅此处获取示例自定义视图,该视图使用TranslateAnimation.

回答by Seb

So, I was looking for the answer to this for my Xamarin project, but I guess it should also apply to Java. The realization I had was that the LinearLayout being animated ALWAYS had the same position (say, it was at x=100, y==100) and your animations should be RELATIVE to this position. The ObjectAnimator was definitely the way to go, and here is my solution:

所以,我一直在为我的 Xamarin 项目寻找答案,但我想它也应该适用于 Java。我意识到被动画化的 LinearLayout 始终具有相同的位置(例如,它在 x=100,y==100)并且您的动画应该相对于这个位置。ObjectAnimator 绝对是要走的路,这是我的解决方案:

First off, a simple layout with some text at the top and a LinearLayout below that which is the target for the animation....

首先,一个简单的布局,顶部有一些文本,下面是一个 LinearLayout,这是动画的目标....

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:p1="http://schemas.android.com/apk/res/android"
    p1:minWidth="25px"
    p1:minHeight="25px"
    p1:layout_width="match_parent"
    p1:layout_height="match_parent"
    p1:id="@+id/frameLayout1">
    <TextView
        p1:text="Some text at the top"
        p1:textAppearance="?android:attr/textAppearanceLarge"
        p1:id="@+id/txtSomeTextAtTheTop"
        p1:layout_width="wrap_content"
        p1:layout_height="wrap_content"
        p1:layout_gravity="center_horizontal" />
    <LinearLayout
        p1:orientation="vertical"
        p1:minWidth="25px"
        p1:minHeight="25px"
        p1:layout_width="wrap_content"
        p1:layout_height="wrap_content"
        p1:id="@+id/linMySlider"
        p1:layout_gravity="center_horizontal|bottom">
        <LinearLayout
            p1:orientation="horizontal"
            p1:minWidth="25px"
            p1:minHeight="25px"
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/linAlwaysDisplay"
            p1:layout_marginBottom="10px">
            <TextView
                p1:text="ALWAYS ON DISPLAY"
                p1:textAppearance="?android:attr/textAppearanceLarge"
                p1:id="@+id/txtAlwaysDisplay"
                p1:layout_width="wrap_content"
                p1:layout_height="wrap_content"
                p1:layout_gravity="center_horizontal" />
        </LinearLayout>
        <LinearLayout
            p1:orientation="horizontal"
            p1:minWidth="25px"
            p1:minHeight="25px"
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/linToHideLineOne">
            <TextView
                p1:text="To Hide Line One"
                p1:textAppearance="?android:attr/textAppearanceLarge"
                p1:id="@+id/txtHideLineOne"
                p1:layout_width="wrap_content"
                p1:layout_height="wrap_content"
                p1:layout_gravity="center_horizontal" />
        </LinearLayout>
        <LinearLayout
            p1:orientation="horizontal"
            p1:minWidth="25px"
            p1:minHeight="25px"
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/linHideLineTwo">
            <TextView
                p1:text="To Hide Line Two"
                p1:textAppearance="?android:attr/textAppearanceLarge"
                p1:id="@+id/txtHideLineTwo"
                p1:layout_width="wrap_content"
                p1:layout_height="match_parent" />
        </LinearLayout>
    </LinearLayout>
</FrameLayout>

My activity, then, looked like the following:

然后,我的活动如下所示:

using System;

using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
using Android.Animation;
using Android.Views.Animations;
using Android.Util;

namespace MyNamespace
{
    [Activity(Label = "testActivity")]
    public class testActivity : Activity
    {
        public static string TAG = "M:testActivity";


        //by default we want the slider to be closed, which is why
        // _sliderOpen has been set to true and we animate it into position when
        //the window gets first focus
        private bool _sliderOpen = true;

        private ViewGroup _linMySlider;
        private LinearLayout _linAlwaysDisplays;

        private int _distanceToTravel;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.testLayout);

            _linMySlider = FindViewById<ViewGroup>(Resource.Id.linMySlider);
            _linAlwaysDisplays = FindViewById<LinearLayout>(Resource.Id.linAlwaysDisplay);

            TextView alwaysDisplayText = FindViewById<TextView>(Resource.Id.txtAlwaysDisplay);
            alwaysDisplayText.Click += AlwaysDisplayText_Click;
        }

        private void AlwaysDisplayText_Click(object sender, EventArgs e)
        {
            DoAnimation(500);
        }

        public override void OnWindowFocusChanged(bool hasFocus)
        {
            base.OnWindowFocusChanged(hasFocus);

            if (hasFocus)
            {
                if (_sliderOpen)
                {
                    //we store this one time as it remains constant throught our sliding animations
                    _distanceToTravel = _linMySlider.Height - _linAlwaysDisplays.Height;
                    DoAnimation(1);
                }
            }
        }

        private void DoAnimation(long duration)
        {
            ObjectAnimator slideMe = null;

            try
            {
                switch (_sliderOpen)
                {
                    case true:
                        slideMe = ObjectAnimator.OfFloat(_linMySlider, "translationY", 0, _distanceToTravel);
                        _sliderOpen = false;
                        break;
                    case false:
                        slideMe = ObjectAnimator.OfFloat(_linMySlider, "translationY", _distanceToTravel, 0);
                        _sliderOpen = true;
                        break;
                }
                slideMe.SetInterpolator(new OvershootInterpolator());
                slideMe.SetDuration(duration);
                slideMe.Start();
            }
            catch (Exception e)
            {
                Log.Error(TAG, "DoAnimation: Exception - " + e.Message);
            }
        }
    }
}

The most important point to note is that the _distanceToTravel (in this case translating on the Y axis) is relative to the Top property of the LinearLayout we are animating. Assume that each of the LinearLayouts that hold the text (ALWAYS ON DISPLAY, To Hide Line One, To Hide Line Two) have a height of 20 (making the total height 60). The slider, say, has a Top property of 2100. Because it is located at the bottom, to hide the two lines means we have to move the LinearLayout linMySlider down by 40 in order to hide the two lines, leaving just the first visible. If you think of the LinearLayout as ALWAYS being 2100 it then makes sense that on the slide down we add 40 to it (well, not us, the Animator does this for us), evident in the first OfFloat line, where the start Y position is 0 (ie 0 relative to 2100, so equals 2100) and its end Y position is _distanceToTravel (which is 40, but again relative so equals, in fact, 2140). In the opposite direction we start with the _distanceToTravel for the Y (again 40, but in fact 2140) and we end at 0 (you guessed it 0 away from 2100 and, therefore, 2100).

需要注意的最重要的一点是 _distanceToTravel(在这种情况下在 Y 轴上平移)是相对于我们正在设置动画的 LinearLayout 的 Top 属性。假设保存文本的每个 LinearLayouts(始终显示、隐藏第一行、隐藏第二行)的高度为 20(使总高度为 60)。例如,滑块的 Top 属性为 2100。因为它位于底部,所以要隐藏两条线意味着我们必须将 LinearLayout linMySlider 向下移动 40 以隐藏两条线,只留下第一条可见。如果您认为 LinearLayout 始终为 2100,那么在向下滑动时我们将其添加 40(好吧,不是我们,动画师为我们执行此操作),这在第一行 OfFloat 中很明显,其中开始 Y 位置是 0(即 0 相对于 2100,so 等于 2100) 并且它的 Y 端位置是 _distanceToTravel(它是 40,但同样是相对的,所以实际上等于 2140)。在相反的方向,我们从 Y 的 _distanceToTravel 开始(同样是 40,但实际上是 2140),我们以 0 结束(你猜它离 2100 是 0,因此是 2100)。

Hope this all makes sense - it took me a little while for the penny to drop, but it works really well with no flicker and no reset back to the original position (which it always had lol). Hopefully the same applies to Java code as it does in this C# example.

希望这一切都是有道理的 - 我花了一点时间才放下一分钱,但它工作得很好,没有闪烁,也没有重置回原始位置(它总是有哈哈)。希望这同样适用于 Java 代码,就像在这个 C# 示例中一样。