保持动画视图在最终动画位置

24

所以我正在使用动画来对一个视图进行动画效果:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator" >

    <translate
        android:duration="1000"
        android:fromYDelta="0%"
        android:toYDelta="-75%" />

</set>

它在视觉上做到了我想要的效果,但我也想冻结此动画的结果。就像我需要以某种方式获取动画后的布局参数(或其他内容?),并将其设置为布局。假设布局Y坐标从0变化到100,但我不知道起始坐标和终点坐标是什么。我需要获取最终坐标并将其设置为布局,因为如果不这样做,则布局在动画后会返回到初始位置,但我需要将其保持在新的位置而不是返回。另外,我需要一个兼容2.3.x的解决方案。
更新1
我也尝试过使用NineOldAndroids:

ObjectAnimator.ofFloat(rootWrapper, "translationY", -rootWrapper.getHeight()*75/100).start();

这将使视图动画,并且视图将保持在动画位置 - 这正是我想要实现的。但是所有控件都保持在它们的位置上,即使布局只能看到其底部25%的部分,剩下的屏幕看起来很空。如果我点击空白屏幕区域,那么按钮(之前存在)的onClick将被触发。如果设置xml-animtaions,则情况也是如此

animation.setFillAfter(true); 
如何解决此问题?所有控件都必须随视图移动。
更新2
代码:
    public void showAbout(){

        final Animation animShow = AnimationUtils.loadAnimation(this, R.anim.about_in_from_bottom);
        animShow.setFillAfter(true); 

        //ObjectAnimator.ofFloat(rootWrapper, "translationY", -rootWrapper.getHeight()*75/100).start();

        animShow.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {}

            @Override
            public void onAnimationEnd(Animation animation) {
                LinearLayout.LayoutParams rootlp = (LinearLayout.LayoutParams) rootWrapper.getLayoutParams();
                Log.i(this,"rootlp: h: "+rootlp.topMargin+ " / w: "+rootlp.bottomMargin);
                rootlp.topMargin = -rootlp.height*75/100;
                rootWrapper.setLayoutParams(rootlp);
            }
        });

        rootWrapper.startAnimation(animShow);
    }

我明白问题所在了 - rootlp.height 返回0,因为它像是MATCH_PARENT或类似的东西。它不会返回实际像素,而是代表某个常量的值!所以看起来我需要使用 .getViewTreeObserver().addOnGlobalLayoutListener 来解决这个问题。
好的,通过 getViewTreeObserver() 我获取了 rootWrapper 的真实高度。现在使用公式 rootlp.topMargin = -rootlp.height*75/100; ,我遇到了更多的问题。
1)动画后视图会额外移动(由于设置了 LP)。
2)包含动画视图的控件(随父视图一起向上移动的按钮)虚拟地停留在原位(它们是不可见但可点击的)!在视觉上,这些控件随着动画视图向上移动,我无法到达它们(它们离开了屏幕)。但是如果我点击它们动画前所在的区域,相应的 onClick() 会被触发!多么有趣啊!
更新3
最终我实现了我的目标!问题出在移动视图的子容器上。我认为如果移动某个视图,则其所有子视图也会一起移动。但事实并非如此,我不得不在动画完成后手动移动子视图,以修复幽灵按钮。


为什么你在动画之后尝试获取LayoutParams?你想让View保持在动画的位置吗? - Philipp Jahoda
是的,这正是我想要做的事情。 - Stan
那么请看我的答案并使用setFillAfter(); - Philipp Jahoda
3个回答

54

请考虑setFillAfter(boolean);将其设置为true将使视图停留在动画位置。

Animation anim = new TranslateAnimation(0, 0, 0, 100);

// or like this
Animation anim = AnimationUtils.loadAnimation(this, R.anim.animation_name);

anim.setFillAfter(true); 
anim.setDuration(1000);

使用可从API 14及以上获得的ViewPropertyAnimator,甚至可以更轻松地完成此操作:

int animationpos = 500;
View.animate().y(animationpos).setDuration(1000); // this will also keep the view at the animated position

或者考虑使用 AnimationListener 来在动画执行后精确获取 LayoutParams:

anim.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {}

            @Override
            public void onAnimationRepeat(Animation animation) {}

            @Override
            public void onAnimationEnd(Animation animation) {
                  // get layoutparams here and set it in this example your views 
                  // parent is a relative layout, please change that to your desires

                  RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) View.getLayoutParams();
                  lp.topMargin = yournewtopmargin // use topmargin for the y-property, left margin for the x-property of your view
                  View.setLayoutParams(lp);
                }
            });

    View.startAnimation(anim);

更新:

如何根据动画的百分比值知道视图移动了多少像素?

在你的.xml动画中,百分比值是相对于动画View的尺寸(如宽度和高度)而言的。 因此,你只需要使用你的View的宽度和高度属性(根据使用x或y动画来获取实际动画距离的情况)。

例如:

你的View的宽度为400像素。你的动画将x属性从0%动画到75%,意味着你的视图将向右移动300像素(400 * 0.75)。所以在onAnimationEnd()中,将LayoutParams.leftMargin设置为300,你的View就会拥有期望的位置。


谢谢!anim.setFillAfter(true);可以达到效果,但是所有控件都保持在他们的位置上,相当于看不见。请查看我的问题更新1。 - Stan
请查看我的回答中的AnimationListener和特别是AnimationEnd方法 :) 您可以获取LayoutParams并适当设置它 - Philipp Jahoda
是的,但如何知道动画结果是你的新顶部边距?这是我的问题的一部分,因为在提问之前我已经尝试在onAnimationEnd中设置LP,但卡在要设置的值上。 - Stan
再次感谢,我尝试了rootlp.topMargin = -rootlp.height*100/75; 和rootWrapper.setLayoutParams(rootlp); 但是没有成功 - 不可见的控件仍然存在... - Stan
什么意思是不可见的控件仍然存在?您能在问题中发布您的代码吗? - Philipp Jahoda
好的,你检查过lp.height是否大于0了吗?同时使用lp.topMargin = lp.topMargin + animationdistance; - Philipp Jahoda

2
我发现仅仅将 fillEnabled 和 fillAfter 设置为 TRUE 并不能解决当涉及点击/触摸监听器时的问题。设置 fillEnabled 和 fillAfter 为 true 只是确保视图的像素在正确的最终位置上绘制。然而,假设正在动画化的视图有一个与之关联的 onclick 监听器。在其新可见位置单击视图将不会产生任何作用,但在原始位置单击屏幕将触发 onClick。
以下是如何解决此问题的示例:
假设我们有一个设置了 onClickListener 的视图。还假设视图的边界为 0、0、0、0(左、上、右、下)。 现在假设我们希望将视图向右移动 200。以下代码将演示如何以使得像素和底层视图对象都移动到正确位置的方式进行此操作,从而调整 onClick 被调用的正确位置。
private Animation generateMoveRightAnimation() {
    final Animation animation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 200,
            Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 0);
    animation.setDuration(300);
    animation.setFillAfter(true);
    animation.setFillEnabled(true);

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

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams();
            // change the coordinates of the view object itself so that on click listener reacts to new position
            view.layout(view.getLeft()+200, view.getTop(), view.getRight()+200, view.getBottom());
            repeatLevelSwitch.clearAnimation();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });

    return animation;
}

@BiGGZ 你的意思是我需要添加更多细节吗? - TomTaila
我正在尝试这个,但它不起作用。我的触摸事件仍然被分派到旧位置。而且lp对象也没有被使用到任何地方。 - BiGGZ
1
说实话,我已经有一段时间没有做这个了,但是我强烈建议使用整个范例来开始动画。我建议改为使用ObjectAnimators。这个API比旧的动画更好得多。https://developer.android.com/guide/topics/graphics/prop-animation.html - TomTaila

0

你可以使用NineOldAndroid代理库随心所欲地使用ObjectAnimator。它支持较低的API级别,并使用新API级别的本地库。

只需将其作为LibraryProject导入即可。


1
就像我在问题中提到的那样,我已经尝试过了。这会导致一些关于幽灵控件的问题,这也在我的问题中有所描述。 - Stan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接