我正在创建一个类似SlideDrawer的东西,但具有更多的自定义选项。基本上它是可以工作的,但在动画结束时会出现闪烁。
进一步解释一下,我使用了TranslateAnimation,然后在这个动画完成后它返回到原始位置,如果我设置了setFillAfter,则布局中的按钮会停止工作。如果我侦听onAnimationEnd并将其他布局设置为View.GONE,那么布局会闪烁。根据判断,在动画结束时,视图返回到调用View.GONE之前的原始位置。
任何建议都将非常棒。谢谢
我正在创建一个类似SlideDrawer的东西,但具有更多的自定义选项。基本上它是可以工作的,但在动画结束时会出现闪烁。
进一步解释一下,我使用了TranslateAnimation,然后在这个动画完成后它返回到原始位置,如果我设置了setFillAfter,则布局中的按钮会停止工作。如果我侦听onAnimationEnd并将其他布局设置为View.GONE,那么布局会闪烁。根据判断,在动画结束时,视图返回到调用View.GONE之前的原始位置。
任何建议都将非常棒。谢谢
这里是与此问题相关的实际bug。
基本上,这意味着当将AnimationListener附加到动画时,onAnimationEnd(...)
方法并不是真正有效的。
解决方法是监听您将动画应用于的视图中的动画事件。例如,如果最初您像这样将动画侦听器附加到动画:
mAnimation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation arg0) {
//Functionality here
}
然后将此动画应用于 ImageView
上,如下所示
mImageView.startAnimation(mAnimation);
为了解决这个问题,现在必须创建一个自定义的ImageView
。public Class myImageView extends ImageView {
然后重写View类的onAnimationEnd方法,并在其中提供所有功能。
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
//Functionality here
}
这是解决此问题的正确方法:在重写的View -> onAnimationEnd(...)
方法中提供功能,而不是在附加到动画的AnimationListener的onAnimationEnd(...)
方法中提供功能。
这样做可以正常工作,不再出现动画末尾的闪烁。希望这可以帮到你。
从API 11开始,您可以使用ObjectAnimator
,实际上更改视图属性,例如翻译的情况下,视图将保持在动画结束后到达的位置。
ObjectAnimator objectAnimator= ObjectAnimator.ofFloat(mContent_container, "translationX", startX, endX);
objectAnimator.setDuration(1000);
objectAnimator.start();
更多内容在这里。
上面的Soham的回答对我有用,尽管当我第一次阅读这个线程时,它并不是立即显而易见的,但值得指出的是,您仍然可以通过在视图上设置单独的监听器,在您的View的onAnimationStart()
和onAnimationEnd()
结束时运行。
例如,如果您的代码需要在动画期间禁用按钮:
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);
目前,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();
}
}
}
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);
这样做可以使你的组件保持干净分离,同时仍然获得一个有效的AnimationListener。
使用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
*
* https://dev59.com/8HE85IYBdhLWcg3wtVxT
*/
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);
}
}
这是我的动画代码:
protected void startSplash() {
final FadeableImageView splash = (FadeableImageView) findViewById(R.id.splash);
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setDuration(2000);
splash.startAnimation(fadeOut);
}
摆脱setFillAfter
,只需在onAnimationEnd()
中使用View.GONE
。请参见此处,其中提供了一个示例自定义视图,该视图使用TranslateAnimation
实现滑动面板。
View.GONE
);你随时都可以将其切换为可见状态。 - CommonsWare所以,我在寻找我的Xamarin项目的答案,但我想它也适用于Java。我意识到的是,被动画化的LinearLayout始终具有相同的位置(例如,它位于x = 100,y == 100),您的动画应该相对于此位置。
ObjectAnimator绝对是正确的选择,这是我的解决方案:
首先,一个简单的布局,顶部有一些文本,下面是一个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>
我的活动看起来像下面这样:
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);
}
}
}
}