Вложенные фрагменты исчезают во время переходной анимации

99

Вот сценарий: активность содержит фрагмент A, который , в свою очередь , использует getChildFragmentManager()для добавления фрагментов A1и A2в его onCreateследующим образом:

getChildFragmentManager()
  .beginTransaction()
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

Пока все хорошо, все работает как положено.

Затем мы запускаем следующую транзакцию в Activity:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .replace(R.id.fragmentHolder, new FragmentB())
  .addToBackStack(null)
  .commit()

Во время перехода enterанимация для фрагмента Bработает правильно, но фрагменты A1 и A2 полностью исчезают . Когда мы отменяем транзакцию с помощью кнопки «Назад», они инициализируются правильно и нормально отображаются во время popEnterанимации.

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

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

Эффект, которого я хочу достичь, прост - я хочу exit(или должна быть popExit?) Анимацию на фрагменте.A (anim2) для запуска, анимируя весь контейнер, включая его вложенные дочерние элементы.

Есть ли способ добиться этого?

Edit : Вы можете найти тестовый пример здесь

Edit2 : Спасибо @StevenByle за то, что подтолкнул меня продолжать попытки со статической анимацией. По-видимому, вы можете установить анимацию для каждой операции (не глобально для всей транзакции), что означает, что дочерние элементы могут иметь неопределенный набор статической анимации, в то время как их родитель может иметь другую анимацию, и все это может быть зафиксировано в одной транзакции . См. Обсуждение ниже и обновленный проект тестового примера .

Делян
источник
Что R.id.fragmentHolderотносительно A, A1, A2 и т. Д.?
CommonsWare
fragmentHolder - это идентификатор в макете действия, фрагмент {One, Two} Holder находится в макете фрагмента A. Все три разные. Фрагмент A был первоначально добавлен в fragmentHolder (т.е. фрагмент B заменяет фрагмент A).
Делян
Я создал здесь образец проекта: github.com/BurntBrunch/NestedFragmentsAnimationsTest , в репозиторий также включен apk. Это действительно досадная ошибка, и я ищу способ ее обойти (при условии, что ее нет в моем коде).
Делян
Теперь я знаю немного больше об этой проблеме. Причина исчезновения фрагментов заключается в том, что дочерние элементы обрабатывают события жизненного цикла раньше родительского. По сути, A1 и A2 удаляются до A, и, поскольку для них не установлена ​​анимация, они внезапно исчезают. Способ несколько смягчить это - явное удаление A1 и A2 в транзакции, которая заменяет A. Таким образом, они анимируются при выходе, однако их скорость анимации возводится в квадрат, поскольку родительский контейнер также анимируется. Было бы признательно за решение, которое не создает этот артефакт.
Делян
Изменение (замена стартового фрагмента), которое вы упомянули в вопросе, является реальным, которое вы хотите сделать, или это просто пример? Вы вызовете changeFragmentметод только один раз?
Luksprog

Ответы:

37

Чтобы пользователь не видел, как вложенные фрагменты исчезают при удалении / замене родительского фрагмента в транзакции, вы можете «смоделировать» эти фрагменты, которые все еще присутствуют, предоставив их изображение в том виде, в каком они появляются на экране. Это изображение будет использоваться в качестве фона для контейнера вложенных фрагментов, поэтому, даже если представления вложенного фрагмента исчезнут, изображение будет имитировать их присутствие. Кроме того, я не вижу проблемы в потере интерактивности с представлениями вложенных фрагментов, потому что я не думаю, что вы хотите, чтобы пользователь действовал на них, когда они только что удаляются (возможно, как действие пользователя, например хорошо).

Я сделал небольшой пример с настройкой фонового изображения (что-то базовое).

Люкспрог
источник
1
Я решил наградить вас наградой, так как именно это решение я и использовал. Большое вам спасибо за ваше время!
Делян
17
Вау, как грязно. На что нам, разработчикам Android, приходится идти ради некоторой гладкости
Дин Уайлд,
1
У меня есть Viewpager из второй вкладки, я заменяю другой фрагмент, и когда я нажимаю на него, мне нужно показать вторую вкладку viewpager, она открывается, но показывает пустую страницу. Я пробовал то, что вы предлагали в приведенной выше теме, но все равно.
Хариш
Проблема остается, когда пользователь возвращается, как написал @Harish
Ewoks
1
Вау серьезно? это 2018 год, а это все еще актуально? :(
Archie G. Quiñones
69

Таким образом, кажется, что для этого существует много разных обходных путей, но, основываясь на ответе @ Jayd16, я думаю, что нашел довольно надежное универсальное решение, которое по-прежнему позволяет настраивать анимацию перехода для дочерних фрагментов и не требует выполнения растровый кеш макета.

Есть BaseFragment класс, который расширяется Fragment, и сделайте так, чтобы все ваши фрагменты расширяли этот класс (а не только дочерние фрагменты).

В этом BaseFragment классе добавьте следующее:

// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    final Fragment parent = getParentFragment();

    // Apply the workaround only if this is a child fragment, and the parent
    // is being removed.
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

private static long getNextAnimationDuration(Fragment fragment, long defValue) {
    try {
        // Attempt to get the resource ID of the next animation that
        // will be applied to the given fragment.
        Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
        nextAnimField.setAccessible(true);
        int nextAnimResource = nextAnimField.getInt(fragment);
        Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);

        // ...and if it can be loaded, return that animation's duration
        return (nextAnim == null) ? defValue : nextAnim.getDuration();
    } catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
        Log.w(TAG, "Unable to load next animation from parent.", ex);
        return defValue;
    }
}

К сожалению, это требует размышлений; однако, поскольку этот обходной путь предназначен для библиотеки поддержки, вы не рискуете изменить базовую реализацию, если не обновите библиотеку поддержки. Если вы создаете библиотеку поддержки из исходного кода, вы можете добавить аксессор для следующего идентификатора ресурса анимации вFragment.java и устранить необходимость в отражении.

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

Кевин Коппок
источник
5
Это мое любимое решение в ветке. Не требует растрового изображения, не требует дополнительного кода в дочернем фрагменте и на самом деле не передает информацию из родительского в дочерний фрагмент.
jacobhyphenated
1
@EugenPechanec Вам нужен nextAnim из родительского фрагмента, а не из дочернего. В этом весь смысл.
Кевин Коппок,
1
К сожалению, этот подход вызывает утечку памяти для дочерних фрагментов и неявно для родительского фрагмента, а также в версиях Android ниже Lollipop :(
Cosmin
8
Спасибо за это очень полезное решение, однако для работы с текущей библиотекой поддержки требуется небольшое обновление (27.0.2, не знаю, какая версия нарушила этот код). mNextAnimтеперь внутри mAnimationInfoобъекта. Вы можете получить к нему доступ так:Field animInfoField = Fragment.class.getDeclaredField("mAnimationInfo"); animInfoField.setAccessible(true); Object animationInfo = animInfoField.get(fragment); Field nextAnimField = animationInfo.getClass().getDeclaredField("mNextAnim");
Дэвид Лериколайс
5
@DavidLericolais, хотел бы добавить еще одну строку кода после вашей. val nextAnimResource = nextAnimField.getInt(animationInfo);заменить строчкуint nextAnimResource = nextAnimField.getInt(fragment);
tingyik90
32

Я смог придумать довольно чистое решение. ИМО это наименее хакерский, и хотя технически это решение «нарисовать растровое изображение», по крайней мере, оно абстрагируется фрагментом lib.

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

private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
    dummyAnimation.setDuration(500);
}

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter && getParentFragment() != null){
        return dummyAnimation;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

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

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

Jayd16
источник
Это помогает, спасибо. Значение продолжительности времени не имеет значения
iscariot
Лиран Коэн
16

Я публикую свое решение для ясности. Решение довольно простое. Если вы пытаетесь имитировать анимацию транзакции родительского фрагмента, просто добавьте настраиваемую анимацию в транзакцию дочернего фрагмента с той же продолжительностью. О, и убедитесь, что вы установили пользовательскую анимацию перед add ().

getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();

Xml для R.anim.none (время анимации входа / выхода моих родителей составляет 250 мс)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>
Maurycy
источник
Я сделал нечто очень похожее, но при обновлении дочернего элемента использовал «показать», а не «добавить». Я также добавил "getChildFragmentManager (). ExecutePendingTransactions ()", хотя я не уверен, что это строго необходимо. Однако это решение работает отлично и не требует «предоставления изображения» фрагмента, как предлагают некоторые.
Брайан Йенчо
Это было здорово. Однако при переключении дочерних фрагментов у меня возникала задержка. чтобы избежать этого, просто установите 2-й параметр без анимации:fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)
ono
7

Я понимаю, что это не может полностью решить вашу проблему, но, возможно, это подойдет кому-то другому, вы можете добавлять enter/ exitи popEnter/ popExitанимации для своих детей, Fragmentкоторые на самом деле не перемещают / анимируют Fragments. Пока анимации имеют ту же продолжительность / смещение, что и их родительские Fragmentанимации, они будут перемещаться / анимироваться вместе с родительской анимацией.

Стивен Байл
источник
1
Я наградил Luksprog наградой, так как его решение работает универсально. Я пробовал трюк со статической анимацией (продолжительность на самом деле не имеет значения - как только родительский элемент исчезает, представления, очевидно, исчезают), но они не работали во всех возможных случаях (см. Мои комментарии под вопросом). Кроме того, этот подход приводит к утечке абстракции, поскольку родительский фрагмент с дочерними элементами должен знать об этом факте и предпринимать дополнительные шаги для установки дочерних анимаций. В любом случае большое спасибо за уделенное время!
Делян
Согласитесь, это скорее обходной путь, чем водонепроницаемое решение, и его можно считать немного хрупким. Но для простых случаев подойдет.
Стивен Байл
4

вы можете сделать это в дочернем фрагменте.

@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
    if (true) {//condition
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
        objectAnimator.setDuration(333);//time same with parent fragment's animation
        return objectAnimator;
    }
    return super.onCreateAnimator(transit, enter, nextAnim);
}
Пэн Гао
источник
Спасибо! Может быть, не лучшее, но, возможно, самое простое решение.
bug56
2

@@@@@@@@@@@@@@@@@@@@@@@@@@@@

РЕДАКТИРОВАТЬ: я закончил тем, что не реализовал это решение, поскольку у него были другие проблемы. Square недавно выпустила 2 библиотеки, заменяющие фрагменты. Я бы сказал, что это может быть лучшая альтернатива, чем попытка взломать фрагменты, чтобы сделать что-то, чего Google не хочет, чтобы они делали.

http://corner.squareup.com/2014/01/mortar-and-flow.html

@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Я подумал, что предлагаю это решение, чтобы помочь людям, у которых возникнет эта проблема в будущем. Если вы проследите разговор с другими людьми на исходном плакате и посмотрите на опубликованный им код, вы увидите, что исходный плакат в конечном итоге приходит к выводу об использовании анимации без операции для дочерних фрагментов при анимации родительского фрагмента. Это решение не идеально, поскольку оно заставляет вас отслеживать все дочерние фрагменты, что может быть громоздким при использовании ViewPager с FragmentPagerAdapter.

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

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

public class ChildFragmentAnimationManager {

private static ChildFragmentAnimationManager instance = null;

private Map<Fragment, List<Fragment>> fragmentMap;

private ChildFragmentAnimationManager() {
    fragmentMap = new HashMap<Fragment, List<Fragment>>();
}

public static ChildFragmentAnimationManager instance() {
    if (instance == null) {
        instance = new ChildFragmentAnimationManager();
    }
    return instance;
}

public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) {
    List<Fragment> children = getChildren(parent);

    ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim);
    for (Fragment child : children) {
        ft.remove(child);
    }

    return ft;
}

public void putChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.add(child);
}

public void removeChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.remove(child);
}

private List<Fragment> getChildren(Fragment parent) {
    List<Fragment> children;

    if ( fragmentMap.containsKey(parent) ) {
        children = fragmentMap.get(parent);
    } else {
        children = new ArrayList<Fragment>(3);
        fragmentMap.put(parent, children);
    }

    return children;
}

}

Затем вам нужен класс, расширяющий фрагмент, который расширяют все ваши фрагменты (по крайней мере, ваши дочерние фрагменты). У меня уже был этот класс, и я назвал его BaseFragment. Когда создается представление фрагментов, мы добавляем его в ChildFragmentAnimationManager и удаляем его при уничтожении. Вы можете сделать это с помощью Attach / Detach или других методов сопоставления в последовательности. Моя логика выбора «Создать / уничтожить представление» заключалась в том, что если у фрагмента нет представления, мне не нужно его анимировать, чтобы его можно было видеть. Этот подход также должен лучше работать с ViewPager, которые используют фрагменты, поскольку вы не будете отслеживать каждый отдельный фрагмент, который удерживает FragmentPagerAdapter, а только 3.

public abstract class BaseFragment extends Fragment {

@Override
public  View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().putChild(parent, this);
    }

    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onDestroyView() {
    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().removeChild(parent, this);
    }

    super.onDestroyView();
}

}

Теперь, когда все ваши фрагменты хранятся в памяти родительским фрагментом, вы можете вызвать для них animate, и ваши дочерние фрагменты не исчезнут.

FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this)
                    .setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
                    .replace(R.id.container, f)
                    .addToBackStack(null)
                    .commit();

Кроме того, чтобы он у вас был, вот файл no_anim.xml, который находится в вашей папке res / anim:

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

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

Spierce7
источник
1

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

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

Представьте, что у нас есть FragmentAи FragmentBс субфрагментами. Теперь, когда вы обычно делаете:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .remove(fragmentA)    <-------------------------------------------
  .addToBackStack(null)
  .commit()

Вместо этого вы делаете

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .hide(fragmentA)    <---------------------------------------------
  .addToBackStack(null)
  .commit()

fragmentA.removeMe = true;

Теперь о реализации Фрагмента:

public class BaseFragment extends Fragment {

    protected Boolean detachMe = false;
    protected Boolean removeMe = false;

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == 0) {
            if (!enter) {
                onExit();
            }

            return null;
        }

        Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        assert animation != null;

        if (!enter) {
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    onExit();
                }

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

        return animation;
    }

    private void onExit() {
        if (!detachMe && !removeMe) {
            return;
        }

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        if (detachMe) {
            fragmentTransaction.detach(this);
            detachMe = false;
        } else if (removeMe) {
            fragmentTransaction.remove(this);
            removeMe = false;
        }
        fragmentTransaction.commit();
    }
}
Данило
источник
Разве popBackStack не вызывает ошибку, потому что пытается показать фрагмент, который был отсоединен?
Alexandre
1

У меня была такая же проблема с фрагментом карты. Он продолжал исчезать во время анимации выхода содержащего его фрагмента. Обходной путь - добавить анимацию для дочернего фрагмента карты, чтобы он оставался видимым во время анимации выхода из родительского фрагмента. Анимация дочернего фрагмента сохраняет альфа-канал на 100% в течение всего периода действия.

Анимация: res / animator / keep_child_fragment.xml

<?xml version="1.0" encoding="utf-8"?>    
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="alpha"
        android:valueFrom="1.0"
        android:valueTo="1.0"
        android:duration="@integer/keep_child_fragment_animation_duration" />
</set>

Затем анимация применяется, когда фрагмент карты добавляется к родительскому фрагменту.

Родительский фрагмент

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.map_parent_fragment, container, false);

    MapFragment mapFragment =  MapFragment.newInstance();

    getChildFragmentManager().beginTransaction()
            .setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0)
            .add(R.id.map, mapFragment)
            .commit();

    return view;
}

Наконец, продолжительность анимации дочернего фрагмента устанавливается в файле ресурсов.

значения / целые числа.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
Евгений
источник
0

Чтобы анимировать исчезновение обработанных фрагментов, мы можем принудительно вернуть стек обратно в ChildFragmentManager. Это запустит анимацию перехода. Для этого нам нужно поймать событие OnBackButtonPressed или прослушать изменения backstack.

Вот пример с кодом.

View.OnClickListener() {//this is from custom button but you can listen for back button pressed
            @Override
            public void onClick(View v) {
                getChildFragmentManager().popBackStack();
                //and here we can manage other fragment operations 
            }
        });

  Fragment fr = MyNeastedFragment.newInstance(product);

  getChildFragmentManager()
          .beginTransaction()
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
                .replace(R.neasted_fragment_container, fr)
                .addToBackStack("Neasted Fragment")
                .commit();
Матеуш Биедрон
источник
0

Недавно я столкнулся с этой проблемой в своем вопросе: Вложенные фрагменты переходят неправильно

У меня есть решение, которое решает эту проблему без сохранения растрового изображения, использования отражения или каких-либо других неудовлетворительных методов.

Пример проекта можно посмотреть здесь: https://github.com/zafrani/NestedFragmentTransitions.

GIF с эффектом можно посмотреть здесь: https://imgur.com/94AvrW4

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

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

   override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
    if (isConfigChange) {
        resetStates()
        return nothingAnim()
    }

    if (parentFragment is ParentFragment) {
        if ((parentFragment as BaseFragment).isPopping) {
            return nothingAnim()
        }
    }

    if (parentFragment != null && parentFragment.isRemoving) {
        return nothingAnim()
    }

    if (enter) {
        if (isPopping) {
            resetStates()
            return pushAnim()
        }
        if (isSuppressing) {
            resetStates()
            return nothingAnim()
        }
        return enterAnim()
    }

    if (isPopping) {
        resetStates()
        return popAnim()
    }

    if (isSuppressing) {
        resetStates()
        return nothingAnim()
    }

    return exitAnim()
}

Действие и родительский фрагмент отвечают за установку состояний этих логических значений. Легче увидеть, как и где из моего примера проекта.

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

зафрани
источник
0

Простой способ решить эту проблему - использовать Fragmentкласс из этой библиотеки вместо класса фрагмента стандартной библиотеки:

https://github.com/marksalpeter/contract-fragment

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

Марк Солпитер
источник
0

Из приведенного выше ответа @kcoppock,

если у вас есть Activity-> Fragment-> Fragments (множественная укладка, помогает следующее), небольшое редактирование лучшего ответа IMHO.

public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {

    final Fragment parent = getParentFragment();

    Fragment parentOfParent = null;

    if( parent!=null ) {
        parentOfParent = parent.getParentFragment();
    }

    if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}
Александру
источник
0

Моя проблема заключалась в удалении родительского фрагмента (ft.remove (фрагмент)), дочерние анимации не выполнялись.

Основная проблема заключается в том, что дочерние фрагменты немедленно УНИЧТОЖАЮТСЯ ДО того, как родительский фрагмент завершил анимацию.

Пользовательские анимации дочерних фрагментов не выполняются при удалении родительского фрагмента

Как и другие ускользают, спрятать РОДИТЕЛЯ (а не ребенка) перед удалением РОДИТЕЛЯ - лучший способ.

            val ft = fragmentManager?.beginTransaction()
            ft?.setCustomAnimations(R.anim.enter_from_right,
                    R.anim.exit_to_right)
            if (parentFragment.isHidden()) {
                ft?.show(vehicleModule)
            } else {
                ft?.hide(vehicleModule)
            }
            ft?.commit()

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

Кстати, вам не нужны пользовательские анимации для дочернего фрагмента, поскольку они наследуют родительские анимации.

Эд Маннерс
источник
0

Старая ветка, но на случай, если кто-то здесь наткнется:

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

val currentFragment = supportFragmentManager.findFragmentByTag(TAG)
val transaction = supportFragmentManager
    .beginTransaction()
    .setCustomAnimations(anim1, anim2, anim1, anim2)
    .add(R.id.fragmentHolder, FragmentB(), TAG)
if (currentFragment != null) {
    transaction.hide(currentFragment).commit()
    Handler().postDelayed({
        supportFragmentManager.beginTransaction().remove(currentFragment).commit()
    }, DURATION_OF_ANIM)
} else {
    transaction.commit()
}

Мы просто скрываем текущий фрагмент и добавляем новый фрагмент, по окончании анимации удаляем старый фрагмент. Таким образом, он обрабатывается в одном месте, и растровое изображение не создается.

джеппмен
источник