在ViewPager和setFillAfter上使用动画

时间:2013-05-13 11:49:49

标签: android android-viewpager android-animation

我有一个ViewPager,我需要在按下按钮时整体移动。我为此使用动画。

当我按下它时,我会翻译'x'。我使用setFillAfter(true)来保持新的位置。 但是当我更改ViewPager的页面时,它会跳回原来的x位置!

我只在Android 4.1上看到过这个问题,用Android 4.0没有问题!所以它看起来像Android中的某种回归。

我附上了一个测试项目,在那里我可以重现这个问题而不需要我周围的所有其他东西。我认为最好是帮助我解决这个问题,以便在Eclipse中导入项目并亲自查看。

我还添加了视频,一个在我的HTC One X上,我看到了这个问题,另一个在平板电脑上安装了Android 4.0,问题不在那里。

我一直在拼命想要解决这个丑陋的副作用,但直到现在还没有运气......

(对不起大电影文件...)

Video of Android 4.0 without the side effect

Video Android 4.1 with the side effect

the project where you can reproduce the issue with

编辑:

我添加了以下内容:

@Override
public void onAnimationEnd(Animation animation) {
    RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
    if (!i)
        lp.setMargins(300,0,0,0);
    else
        lp.setMargins(0,0,0,0);

    myViewPager.setLayoutParams(lp);
}

之后它会保持在正确的位置,但它会快速闪烁,就像动画在结尾处仍然显示一样,当我更改边距时,它仍会显示动画后的偏移量。然后它跳到正确的位置。

4 个答案:

答案 0 :(得分:3)

主要问题似乎是动画类型选择不正确。您会看到,View Animation作为工具无意与ViewPager等复杂交互式对象一起使用。它仅提供绘图场所的低成本动画。动画ViewPager响应用户动作的视觉行为是未定义的,不应该依赖它。 丑陋的电影,当你用真实物体代替“gost”时,这是很自然的。

在您的案例中使用预期的机制,因为API 11是构建在视图中的专用属性动画制作工具,用于优化性能: ViewPropertyAnimator ,或者不是专门的,但更多多功能的ObjectAnimator和AnimatorSet。

Property animation使View真正改变了它的位置和功能。

要创建项目,使用ViewPropertyAnimator,请将您的侦听器设置更改为:

btn.setOnClickListener(new OnClickListener() {
    boolean b = false;
    @Override
    public void onClick(View v) {
        if(b) {
            myViewPager.animate().translationX(0f).setDuration(700);
        }
        else {
            myViewPager.animate().translationX(300f).setDuration(700);
        }
        b=!b;
    }
});

如果您只想使用xml配置,请坚持使用| ObjectAnimator和AnimatorSet。请阅读以上链接以获取更多信息。

如果您急于支持预蜂窝设备,可以使用Jake Warton的NineOldAndroids项目。希望有所帮助。

答案 1 :(得分:1)

那是因为动画的setFillAfter(true)实际上并没有改变View的位置或任何属性;它所做的只是创建视图的绘图缓存的位图,并将其留在动画结束的位置。一旦屏幕再次失效(即更改ViewPager中的页面),位图将被删除,它将显示为View返回其原始位置,而实际上它已经存在。

如果希望视图在动画结束后保留​​其位置,则需要实际调整View的LayoutParams以匹配您想要的效果。为此,您可以覆盖Animation的onAnimationEnd方法,并在那里调整View的LayoutParams。

调整LayoutParams后,您可以删除对setFillAfter(true)的调用,并且您的View实际上会保留在您预期的位置。

答案 2 :(得分:1)

关于闪烁问题:

之前我遇到过这个问题,它源于onAnimationEnd()调用未能与下一个布局传递同步的可能性。 Animation通过将变换应用于视图,相对于其当前位置绘制它来工作。

但是,在您使用onAnimationEnd()方法移动视图后,可以呈现视图。在这种情况下,动画的变换仍然正确应用,但动画认为视图没有改变其原始位置,这意味着它将相对于其ENDING位置而不是其STARTING位置绘制。

我的解决方案是创建动画的自定义子类并添加一个方法changeYOffset(int change),该方法修改在动画的applyTransformation方法中应用的y平移。我在View的onLayout()方法中调用了这个新方法,并传递了新的y-offset。

以下是我的动画中的一些代码MenuAnimation

/**
* Signal to this animation that a layout pass has caused the View on which this animation is
* running to have its "top" coordinate changed.
*
* @param change
* the difference in pixels
*/
public void changeYOffset(int change) {
    fromY -= change;
    toY -= change;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float reverseTime = 1f - interpolatedTime;

    float dy = (interpolatedTime * toY) + (reverseTime * fromY);
    float alpha = (interpolatedTime * toAlpha) + (reverseTime * fromAlpha);

    if (alpha > 1f) {
        alpha = 1f;
    }
    else if (alpha < 0f) {
        alpha = 0f;
    }

    t.setAlpha(alpha);
    t.getMatrix().setTranslate(0f, dy);
}

来自View类:

private int lastTop;

// ...

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // the animation is expecting that its View will not be moved by the container
    // during its time period. if this does happen, we need to inform it of the change.
    Animation anim = getAnimation();
    if (anim != null && anim instanceof MenuAnimation) {
        MenuAnimation animation = (MenuAnimation) anim;
        animation.changeYOffset(top - lastTop);
    }

    // ...

    lastTop = top;

    super.onLayout(changed, left, top, right, bottom);
}

答案 3 :(得分:1)

Crucero对setFill没有调整params后无效。当视图被重新布局时(它将在无效之后发生),它的布局参数将是总是应用的,因此它应该返回到原始位置。

Jschools关于onAnimationEnd是正确的。强烈建议您使用调试器逐步执行source code,在那里您将发出更新,在onAnimationEnd被触发后影响视图的绘制位置,此时您实际应用了布局参数,因此偏移倍增引起的闪烁。

但是这可以通过确保你在正确的时间重新布局来解决。您希望在动画结束时将重新定位逻辑放在ui消息队列的末尾,以便在动画之后但在布局之前轮询它。没有任何地方建议这样做,令人讨厌,但我还没有找到任何SDK发布原因的理由(当这样做只有一次并且没有错误地使用ui线程时)这应该不起作用。

由于我们在某些旧设备上发现的另一个问题,也请清除动画。

所以,试试:

@Override
public void onAnimationEnd(final Animation animation) {
   myViewPager.post(new Runnable() {
       @Override
       public public void run() {
           final RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
           if (!someBooleanIPresume)
               lp.setMargins(300,0,0,0);
           else
               lp.setMargins(0,0,0,0);

           myViewPager.setLayoutParams(lp);
           myViewPager.clearAnimation();
       }
}