Использование анимации на ViewPager и setFillAfter

У меня есть ViewPager, который мне нужно перемещать целиком при нажатии кнопки. Я использую для этого анимацию.

Когда я нажимаю на нее, я перевожу для нее «x». Я использую setFillAfter(true), чтобы сохранить новую позицию. Но когда я меняю страницу ViewPager, она возвращается к исходной x-позиции!

Я видел эту проблему только на Android 4.1, с Android 4.0 проблем нет! Так что это похоже на какую-то регрессию в Android.

Я приложил тестовый проект, в котором я мог воспроизвести проблему без всего остального. Я думаю, что лучше всего, если вы хотите помочь мне понять это, импортировать проект в свой Eclipse и увидеть его своими глазами.

Я также добавил видео, одно на моем HTC One X, где я вижу проблему, а другое на планшете с Android 4.0, где проблемы нет.

Я отчаянно пытался исправить этот уродливый побочный эффект, но пока безуспешно...

(Извините за большие файлы фильмов...)

Видео Android 4.0 без побочных эффектов

Видео Android 4.1 с побочным эффектом

проект, в котором вы можете воспроизвести проблему

Редактировать:

Я добавил следующее:

@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);
}

После этого он остается в правильном положении, но быстро «мерцает», как будто анимация все еще отображается в конце, и когда я меняю поле, он все еще показывает смещение, которое было после анимации. Затем он прыгает в правильное положение.


person Boy    schedule 13.05.2013    source источник
comment
Не могли бы вы изменить первое предложение с move на, скажем, move в целом, чтобы читателям было понятно, чего вы хотите достичь.   -  person alexei burmistrov    schedule 23.05.2013
comment
сделано, спасибо за замечание   -  person Boy    schedule 27.05.2013


Ответы (4)


Основная проблема заключается в неправильном выборе типа анимации. Видите ли, View Animation как инструмент не предназначен для использования со сложными интерактивными объектами, такими как ViewPager. Он предлагает только недорогую анимацию места рисования видов. Визуальное поведение анимированного ViewPager в ответ на действия пользователя не определено, и на него нельзя полагаться. Уродливые щелчки, когда вы заменяете «гост» реальным объектом, вполне естественны.

Механизм, который предназначен для использования в вашем случае, поскольку API 11 — это специализированный аниматор свойств, встроенный в Views для оптимизации производительности: ViewPropertyAnimator или неспециализированный, но более универсальный ObjectAnimator и АниматорСет.

Анимация свойства заставляет вид действительно менять свое место и там нормально работают.

Чтобы создать проект, чтобы использовать, скажем, 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. Прочтите приведенную выше ссылку для получения дополнительной информации.

В случае, если вы хотите поддерживать устройства до Honeycomb, вы можете использовать проект Jake Warton NineOldAndroids. Надеюсь, это поможет.

person alexei burmistrov    schedule 23.05.2013
comment
Это потрясающий человек! Большое спасибо, это именно то, что мне нужно!! Даже когда сейчас выполняется перевод, вы можете листать страницы. Спасибо, что нашли время импортировать проект. Вы получили награду? Раньше у меня не было времени, чтобы попробовать это и поставить баллы до крайнего срока ... - person Boy; 27.05.2013

Это связано с тем, что setFillAfter(true) анимации фактически не меняет положение или какие-либо атрибуты представления; все, что он делает, это создает растровое изображение кэша отрисовки вида и оставляет его там, где заканчивается анимация. Как только экран снова станет недействительным (т. е. изменится страница в ViewPager), растровое изображение будет удалено, и будет казаться, что представление возвращается в исходное положение, хотя на самом деле оно уже было там.

Если вы хотите, чтобы представление сохраняло свою позицию после завершения анимации, вам необходимо настроить LayoutParams представления в соответствии с желаемым эффектом. Для этого вы можете переопределить метод onAnimationEnd анимации и настроить внутри него LayoutParams представления.

После того, как вы настроите LayoutParams, вы можете удалить свой вызов setFillAfter(true), и ваше представление фактически останется там, где вы ожидаете.

person Cruceo    schedule 13.05.2013
comment
Разве layoutparams не только для ширины и высоты, а не для позиционирования x/y? - person Boy; 13.05.2013
comment
Зависит от вашего макета. Если вы делаете RelativeLayout, вы можете установить что-то вроде левого поля по ширине экрана, если вы пытаетесь сохранить его за пределами границ. Если AbsoluteLayout (устарело), ​​вы можете установить x/y. Если вы просто не хотите его видеть, вы можете даже просто скрыть вид, установив его видимость в onAnimationEnd на View.GONE, и если вам когда-нибудь понадобится снова, вы можете просто вернуть видимость на View.VISIBLE в onAnimationStart, а затем анимируйте его соответствующим образом. Есть много способов сделать это, но setFillAfter(true) определенно не подходит. - person Cruceo; 13.05.2013
comment
Тогда почему etFillAfter(true) отлично работает на 4.0, а не на 4.1...разочаровывает...это какая-то регрессия...и посмотрите мое редактирование, я добавил layoutparams и теперь у меня мерцание/дергание в конце анимации и установка поля... - person Boy; 13.05.2013
comment
Хммм, я думаю, вам действительно следует использовать setFillAfter(true), чтобы убедиться, что он не прыгает, в сочетании с измененными параметрами LayoutParams, чтобы убедиться, что позиция остается. Моя ошибка, что я сказал взять его - person Cruceo; 13.05.2013
comment
Возможно, чтобы избавиться от прыжка, попробуйте установить его видимость на GONE в onAnimationEnd, а затем вы можете вернуть его видимость после того, как вы настроили параметры макета. - person Cruceo; 14.05.2013
comment
Я уже сделал его невидимым, затем установил новую позицию, а затем стал видимым, все та же проблема. - person Boy; 27.05.2013

По поводу мерцания:

Я сталкивался с этой проблемой раньше, и она связана с возможностью того, что вызов onAnimationEnd() не синхронизируется со следующим проходом макета. Animation работает, применяя преобразование к представлению, рисуя его относительно его текущей позиции.

Однако представление может быть отображено после того, как вы переместили его в свой метод onAnimationEnd(). В этом случае преобразование анимации по-прежнему применяется правильно, но анимация считает, что представление не изменило свое исходное положение, что означает, что оно будет отрисовано относительно своего КОНЕЧНОГО положения, а не его НАЧАЛЬНОГО положения.

Мое решение состояло в том, чтобы создать собственный подкласс анимации и добавить метод changeYOffset(int change), который изменяет перевод y, который применяется во время метода анимации applyTransformation. Я вызываю этот новый метод в методе onLayout() моего представления и передаю новое смещение по оси y.

Вот часть моего кода из моей анимации, 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);
}
person Jschools    schedule 21.05.2013

Crucero прав в том, что setFillAfter не настраивает аннулирование параметров после публикации. При повторном размещении представления (что произойдет через проход после того, как оно станет недействительным), его параметры макета будут теми, которые применялись всегда, поэтому оно должно вернуться в исходное положение.

И Jschools прав насчет onAnimationEnd. Настоятельно рекомендуем вам пройти через исходный код с отладчиком, где вы обнаружите, что сделано обновление, влияющее на отрисовываемую позицию представления после запуска onAnimationEnd, после чего вы фактически применили параметры макета, поэтому мерцание вызвано удвоением смещения.

Но эту проблему можно решить довольно просто, убедившись, что вы выполняете ретрансляцию в нужное время. Вы хотите поместить свою логику повторного позиционирования в конец очереди сообщений пользовательского интерфейса во время окончания анимации, чтобы она опрашивалась после анимации, но перед размещением. Нигде не предлагается сделать это, что раздражает, но я все же нашел причину в любом выпуске SDK, почему (когда это делается только один раз и не неправильно используется поток пользовательского интерфейса) это не должно работать.

Также удалите анимацию из-за другой проблемы, обнаруженной нами на некоторых старых устройствах.

Так что постарайтесь:

@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();
       }
}
person Tom    schedule 22.05.2013