web-dev-qa-db-ja.com

遷移アニメーション中にネストされたフラグメントが消える

シナリオは次のとおりです。アクティビティにはフラグメントAが含まれており、フラグメントはgetChildFragmentManager()を使用してフラグメントA1およびA2そのonCreateのように:

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

これまでのところ、非常に良好で、すべてが期待どおりに実行されています。

次に、アクティビティで次のトランザクションを実行します。

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

移行中、フラグメントenterBアニメーションは正しく実行されますが、フラグメント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:テストケースを見つけてください here

Edit2:@StevenByleに、静的なアニメーションを試し続けてくれてありがとう。どうやら、操作ごとにアニメーションを設定できます(トランザクション全体に対してグローバルではありません)。つまり、子は不定の静的アニメーションセットを持つことができ、親は異なるアニメーションを持つことができ、すべてを1つのトランザクションでコミットできます。以下の説明と 更新されたテストケースプロジェクト を参照してください。

98
Delyan

トランザクションで親フラグメントが削除/置換されたときにネストされたフラグメントが消えるのをユーザーが見るのを避けるために、画面に表示される画像を提供することで、それらのフラグメントがまだ存在することを「シミュレート」できます。この画像は、ネストされたフラグメントコンテナの背景として使用されるため、ネストされたフラグメントのビューが消えても、画像はその存在をシミュレートします。また、ネストされたフラグメントのビューとの対話性が問題として失われることはありません。なぜなら、それらが削除されるプロセスにあるときにユーザーがアクションを実行したいとは思わないからです(おそらく、ユーザーアクションとしてまあ)。

背景画像(基本的なもの)を設定して 小さな例 を作成しました。

36
Luksprog

したがって、これには多くの異なる回避策があるようですが、@ 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;
    }
}

残念ながら、リフレクションが必要です。ただし、この回避策はサポートライブラリ向けであるため、サポートライブラリを更新しない限り、基礎となる実装が変更されるリスクはありません。ソースからサポートライブラリを構築する場合、次のアニメーションリソースIDのアクセサーをFragment.Javaに追加し、リフレクションの必要性を削除できます。

このソリューションにより、親のアニメーション期間を「推測」する必要がなくなり(「何もしない」アニメーションは親の終了アニメーションと同じ期間になります)、子フラグメントでカスタムアニメーションを実行できます(例:子アニメーションを別のアニメーションに入れ替えます)。

67
kcoppock

かなりきれいなソリューションを思いつくことができました。 IMOは最もハックが少なく、これは技術的には少なくともフラグメントライブラリによって抽象化された「ビットマップの描画」ソリューションです。

子のfragsがこれで親クラスをオーバーライドすることを確認してください:

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

子フラグに終了アニメーションがある場合、点滅する代わりにアニメーションされます。これを利用するには、アニメーションを作成し、ある期間、フルアルファで子フラグメントを単純に描画します。このようにして、アニメーション化するときに親フラグメント内で表示されたままになり、目的の動作を実現します。

私が考えることができる唯一の問題は、その期間を追跡することです。多分それを大きな数字に設定することもできますが、そのアニメーションをまだどこかに描画すると、パフォーマンスの問題が発生する可能性があります。

30
Jayd16

明確にするためにソリューションを投稿しています。解決策は非常に簡単です。親のフラグメントトランザクションアニメーションを模倣しようとしている場合、単に同じアニメーションのアニメーションを子フラグメントトランザクションに追加します。ああ、add()の前に必ずカスタムアニメーションを設定してください。

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

R.anim.noneのxml(私の両親の入退場のアニメーション時間は250msです)

<?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>
15
Maurycy

私はこれであなたの問題を完全に解決できないかもしれないことを理解していますが、おそらく他の人のニーズに合うでしょう。enter/exitpopEnter/popExitFragmentsを実際に移動/アニメーション化しない子供向けのアニメーションFragments。アニメーションがその親Fragmentアニメーションと同じ持続時間/オフセットを持っている限り、それらは親のアニメーションとともに移動/アニメーションするように見えます。

7
Steven Byle

これは子フラグメントで実行できます。

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

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

編集:私はこれが持っている他の問題があったので、このソリューションを未実装にしました。 Squareは最近、フラグメントを置き換える2つのライブラリを発表しました。これは、フラグメントをハックしてグーグルが望まないことをするよりも、実際には優れた代替手段になると思います。

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、またはシーケンス内の他の一致メソッドで実行できます。ビューの作成/破棄を選択するための私のロジックは、フラグメントにビューがない場合、引き続き表示されるようにアニメーション化する必要がないためです。このアプローチは、FragmentPagerAdapterが保持しているすべてのフラグメントを追跡するのではなく、フラグメントを使用するViewPagersでもうまく機能するはずです。

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

}

すべてのフラグメントが親フラグメントによってメモリに保存されるようになったので、このようにアニメーションを呼び出すことができ、子フラグメントは消えません。

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();

また、ちょうどあなたがそれを持っているので、ここにあなたのres/animフォルダに行くno_anim.xmlファイルがあります:

<?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>

繰り返しますが、このソリューションは完璧ではないと思いますが、子フラグメントを持っているすべてのインスタンスよりも優れており、親フラグメントにカスタムコードを実装して各子を追跡します。私はそこに行ったことがありますが、面白くありません。

2
spierce7

マップフラグメントでも同じ問題が発生していました。含まれているフラグメントの終了アニメーション中に消え続けました。回避策は、子フラグメントのアニメーションを追加することです。これにより、親フラグメントの終了アニメーション中に表示が維持されます。子フラグメントのアニメーションは、その継続期間中、アルファを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;
}

最後に、子フラグメントアニメーションの継続時間がリソースファイルに設定されます。

values/integers.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
1
Evgenii

Luksprogが示唆したように、現在のフラグメントをビットマップにスナップショットするよりも、この問題に対するより良い解決策を見つけたと思います。

トリックはhideフラグメントを削除またはデタッチし、アニメーションが完了した後にのみ、フラグメントは独自のフラグメントトランザクションで削除またはデタッチします。

FragmentAFragmentBがあり、両方ともサブフラグメントがあるとします。今、あなたが通常するとき:

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();
    }
}
1
Danilo

この問題はandroidx.fragment:fragment:1.2.0-alpha02。詳細については、 https://issuetracker.google.com/issues/11667531 をご覧ください。

0

この問題を解決する簡単な方法は、標準ライブラリフラグメントクラスの代わりに、このライブラリのFragmentクラスを使用することです。

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

補足として、パッケージにはContractFragmentという便利なデリゲートパターンも含まれています。これは、親子フラグメントの関係を活用してアプリを構築するのに便利です。

0
Mark Salpeter

ネストされたフラグメントの消失をアニメーション化するために、ChildFragmentManagerで強制的にポップバックスタックすることができます。これにより、遷移アニメーションが発生します。これを行うには、OnBackButtonPressedイベントをキャッチするか、バックスタックの変更をリッスンする必要があります。

コードの例を次に示します。

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
Mateusz Biedron

私は最近私の質問でこの問題に遭遇しました: ネストされたフラグメントが正しく移行しない

私はビットマップを保存せずに、これを解決する解決策を持っています。

サンプルプロジェクトはこちらでご覧いただけます: https://github.com/zafrani/NestedFragmentTransitions

効果のGIFはここで見ることができます: https://imgur.com/94AvrW4

私の例では、6つの子フラグメントがあり、2つの親フラグメントに分割されています。エンター、イグジット、ポップ、プッシュのトランジションを問題なく達成できました。構成の変更とバックプレスも正常に処理されます。

ソリューションの大部分は、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
zafrani

私の問題は、親フラグメントの削除(ft.remove(fragment))で、子アニメーションは発生していませんでした。

基本的な問題は、アニメーションを終了する親フラグメントの前に、子フラグメントが直ちに破棄されることです。

子フラグメントのカスタムアニメーションは、親フラグメントの削除時に実行されません

他の人が逃れたように、親を削除する前に親(子供ではなく)を非表示にする方法があります。

            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()

実際に親を削除する場合は、おそらくカスタムアニメーションにリスナーを設定して、アニメーションがいつ終了するかを確認する必要があります。そうすると、親フラグメントでいくつかのファイナライズを安全に実行できます(削除)。これを行わないと、タイムリーにアニメーションを殺してしまう可能性があります。 N.Bアニメーションは、独自の非同期キューで実行されます。

ところで、親フラグメントを継承するため、子フラグメントにカスタムアニメーションは必要ありません。

0
Ed Manners

上記の@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
alekshandru