Подтвердить что ты не робот

Анимация FragmentTransaction для перехода сверху

Я пытаюсь добиться следующего эффекта, используя FragmentTransaction.setCustomAnimations.

  • Фрагмент A показывает
  • Заменить фрагмент A фрагментом B. Фрагмент A должен оставаться видимым во время замены. Фрагмент B должен скользить справа. Фрагмент B должен скользить в верхней части фрагмента A.

У меня нет проблем с настройкой слайда в анимации. Моя проблема заключается в том, что я не могу понять, как сделать Fragment A, где он находится, и быть UNDER Fragment B во время работы слайда в анимации. Независимо от того, что я делаю, кажется, что Fragment A находится сверху.

Как я могу это достичь?

Вот код FragmentTransaction:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing,
    R.anim.slide_out_right);
ft.replace(R.id.fragment_content, fragment, name);
ft.addToBackStack(name);
ft.commit();

Как вы можете видеть, я определил анимацию R.anim.nothing для анимации "out", потому что на самом деле я не хочу, чтобы Fragment A ничего не делал, кроме как оставаться там, где он находится во время транзакции.

Вот ресурсы анимации:

slide_in_right.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:zAdjustment="top" />

nothing.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_mediumAnimTime"
    android:fromAlpha="1.0"
    android:toAlpha="1.0"
    android:zAdjustment="bottom" />
4b9b3361

Ответ 1

Я нашел решение, которое работает для меня. Я закончил использование ViewPager с FragmentStatePagerAdapter. ViewPager обеспечивает поведение swiping, а FragmentStatePagerAdapter свопирует фрагменты. Последним трюком для достижения эффекта наличия одной страницы, видимой "под" входящей страницей, является использование PageTransformer. PageTransformer переопределяет переход по умолчанию в ViewPager между страницами. Вот пример PageTransformer, который обеспечивает эффект с переводом и небольшое количество масштабирования на левой стороне страницы.

public class ScalePageTransformer implements PageTransformer {
    private static final float SCALE_FACTOR = 0.95f;

    private final ViewPager mViewPager;

    public ScalePageTransformer(ViewPager viewPager) {
            this.mViewPager = viewPager;
    }

    @SuppressLint("NewApi")
    @Override
    public void transformPage(View page, float position) {
        if (position <= 0) {
            // apply zoom effect and offset translation only for pages to
            // the left
            final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR;
            int pageWidth = mViewPager.getWidth();
            final float translateValue = position * -pageWidth;
            page.setScaleX(transformValue);
            page.setScaleY(transformValue);
            if (translateValue > -pageWidth) {
                page.setTranslationX(translateValue);
            } else {
                page.setTranslationX(0);
            }
        }
    }

}

Ответ 2

Я не знаю, нужен ли вам еще ответ, но мне недавно нужно было сделать то же самое, и я нашел способ сделать то, что вы хотите.

Я сделал что-то вроде этого:

FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

MyFragment next = getMyFragment();

ft.add(R.id.MyLayout,next);
ft.setCustomAnimations(R.anim.slide_in_right,0);
ft.show(next);
ft.commit();

Я показываю свой фрагмент в FrameLayout.

Он работает штрафом, но старший фрагмент все еще находится в моем представлении, я позволяю андроиду управлять им, как он хочет, потому что если я поставлю:

ft.remove(myolderFrag);

он не отображается во время анимации.

slide_in_right.xml

    <?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="150" android:fromXDelta="100%p" 
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="0" />
 </set>

Ответ 3

Начиная с Lollipop, вы можете увеличить перевод Z вашего входящего фрагмента. Он появится над выходящим.

Например:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ViewCompat.setTranslationZ(getView(), 100.f);
}

Если вы хотите изменить значение translationZ только для продолжительности анимации, вы должны сделать что-то вроде этого:

@Override
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
    Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim);
    nextAnimation.setAnimationListener(new Animation.AnimationListener() {

        private float mOldTranslationZ;

        @Override
        public void onAnimationStart(Animation animation) {
            if (getView() != null && enter) {
                mOldTranslationZ = ViewCompat.getTranslationZ(getView());
                ViewCompat.setTranslationZ(getView(), 100.f);
            }
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            if (getView() != null && enter) {
                ViewCompat.setTranslationZ(getView(), mOldTranslationZ);
            }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
    return nextAnimation;
}

Ответ 4

После большего количества экспериментов (следовательно, это мой второй ответ) проблема заключается в том, что R.anim.nothing означает "исчезать", когда мы хотим, чтобы другая анимация явно говорила "оставайся". Решение состоит в том, чтобы определить истинную анимацию "ничего не делать":

Сделать файл no_animation.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:anim/linear_interpolator"
        android:fromXScale="1.0"
        android:toXScale="1.0"
        android:fromYScale="1.0"
        android:toYScale="1.0"
        android:duration="200"
        />
    <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="200"
        android:startOffset="200"
        />
</set>

Теперь просто сделайте так, как в противном случае:

getActivity().getSupportFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation)
                .replace(R.id.container, inFrag, FRAGMENT_TAG)
                .addToBackStack("Some text")
                .commit();

Ответ 5

Я нашел альтернативное решение (не сильно проверенное), которое я нахожу более элегантным, чем предложения до сих пор:

final IncomingFragment newFrag = new IncomingFragment();
newFrag.setEnterAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    clearSelection();
                    inFrag.clearEnterAnimationListener();

                    getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

 getActivity().getSupportFragmentManager().beginTransaction()
                    .setCustomAnimations(R.anim.slide_in_from_right, 0)
                    .add(R.id.container, inFrag)
                    .addToBackStack(null)
                    .commit();

Это вызывается из внутреннего класса класса OutgoingFragment.

Вставляется новый фрагмент, анимация завершается, затем удаляется старый фрагмент.

В некоторых приложениях могут быть проблемы с памятью, но это лучше, чем сохранение обоих фрагментов бесконечно.

Ответ 6

FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction();
                ft.setCustomAnimations(0, R.anim.slide_out_to_right);
            if (!fragment.isAdded())
            {
                ft.add(R.id.fragmentContainerFrameMyOrders, fragment);
                ft.show(fragment);
            }
            else
                ft.replace(R.id.fragmentContainerFrameMyOrders, fragment);
            ft.commit();

Ответ 7

Это мое текущее решение для любого, кто интересуется.

В функции добавления нового Fragment:

final Fragment toRemove = fragmentManager.findFragmentById(containerID);
if (toRemove != null) {
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                fragmentManager.beginTransaction().hide(toRemove).commit();
            }
        }, 
        getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100);
        // Use whatever duration you chose for your animation for this handler
        // I added an extra 100 ms because the first transaction wasn't always 
        // fast enough
}
fragmentManager.beginTransaction()
    .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd)
    .addToBackStack(tag).commit();

и в onCreate:

final FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment current = fragmentManager.findFragmentById(containerID);
                if (current != null && current.isHidden()) {
                    fragmentManager.beginTransaction().show(current).commit();
                }
            }
        });

Я бы предпочел какой-то AnimationListener вместо обработчика выше, но я не видел, чтобы вы могли использовать его для обнаружения конца анимации транзакций, которая не была привязана к фрагменту, например onCreateAnimation(). Любые предложения/исправления с соответствующим слушателем будут оценены.

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

Если вы хотите удалить фрагмент, вы можете поместить fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss(); в Handler Runnable, а в OnBackStackChangedListener:

// Use back stack entry tag to get the fragment
Fragment current = getCurrentFragment(); 
if (current != null && !current.isAdded()) {
    fragmentManager.beginTransaction()
        .add(containerId, current, current.getTag())
        .commitNowAllowingStateLoss();
}

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

EDIT: Я обнаружил следующие функции, которые могли бы заменить FragmentTransaction.remove() и FragmentTransaction.add() выше:

FragmentTransaction. detach():

Отсоедините данный фрагмент от пользовательского интерфейса. Это то же самое состояние, что и при наложении на задний стек: фрагмент удаляется из пользовательского интерфейса, однако его состояние по-прежнему активно управляется менеджером фрагментов. При входе в это состояние его иерархия представлений уничтожается.

FragmentTransaction. attach():

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

Ответ 8

Основанный на ответе jfrite, прилагающем мои реализации

import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.util.Log;

public final class AnimationHelper {
    private AnimationHelper() {

    }

    private static String TAG = AnimationHelper.class.getSimpleName();
    private static final float ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING = 100f;
    private static final int RESTORE_ANIMATION_DELAY = 16;

    /**
     * When replacing fragments with animations, by default the new fragment is placed below the replaced fragment. This
     * method returns an animation object that sets high elevation at the beginning of the animation and resets the
     * elevation when the animation completes. The {@link Animation} object that is returned is not the actual object
     * that is used for the animating the fragment but the callbacks are called at the appropriate time. The method
     * {@link Fragment#onCreateAnimation(int, boolean, int)} by default returns null, therefor, this method can be used
     * as the return value for {@link Fragment#onCreateAnimation(int, boolean, int)} method although it can return
     * null.
     * @param enter True if fragment is 'entering'.
     * @param nextAnim Animation resource id that is about to play.
     * @param fragment The animated fragment.
     * @return If nextAnim is a valid resource id and 'enter' is true, returns an {@link Animation} object with the
     * described above behavior, otherwise returns null.
     */
    @Nullable
    public static Animation increaseElevationWhileAnimating(boolean enter, int nextAnim,
                                                            @NonNull Fragment fragment) {
        if (!enter || nextAnim == 0) {
            return null;
        }
        Animation nextAnimation;
        try {
            nextAnimation = AnimationUtils.loadAnimation(fragment.getContext(), nextAnim);
        } catch (Resources.NotFoundException e) {
            Log.e(TAG, "Can't find animation resource", e);
            return null;
        }
        nextAnimation.setAnimationListener(new Animation.AnimationListener() {
            private float oldTranslationZ;

            @Override
            public void onAnimationStart(Animation animation) {
                if (fragment.getView() != null && !fragment.isDetached()) {
                    oldTranslationZ = ViewCompat.getTranslationZ(fragment.getView());
                    ViewCompat.setTranslationZ(fragment.getView(), ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING);
                }
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                if (fragment.getView() != null && !fragment.isDetached()) {
                    fragment.getView().postDelayed(() -> {
                        // Decreasing the elevation at the ned can cause some flickering because of timing issues,
                        // Meaning that the replaced fragment did not complete yet the animation. Resting the animation
                        // with a minor delay solves the problem.
                        if (!fragment.isDetached()) {
                            ViewCompat.setTranslationZ(fragment.getView(), oldTranslationZ);
                        }
                    }, RESTORE_ANIMATION_DELAY);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        return nextAnimation;
    }
}

Вот как я использую вспомогательный фрагмент.

@Override
    public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) {
        return AnimationHelper.increaseElevationWhileAnimating(enter, nextAnim, this);
    }

Вот как я начинаю фрагмент с анимацией

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out);