101

シナリオは次のとおりです。アクティビティには fragment が含まれており、フラグメントを追加するために使用Aされます。getChildFragmentManager()A1A2onCreate

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

私が達成したい効果は単純です -フラグメント( exitanim2 ) のアニメーションを実行して、ネストされた子を含むコンテナー全体をアニメーション化します。popExitA

それを達成する方法はありますか?

編集:ここでテストケースを見つけてください

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

4

16 に答える 16

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

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

このソリューションにより、親のアニメーションの長さを「推測」する必要がなくなり (「何もしない」アニメーションが親の終了アニメーションと同じ長さになるように)、子フラグメントでカスタム アニメーションを引き続き実行できます (たとえば、異なるアニメーションで子フラグメントを再スワップします)。

于 2014-04-24T17:44:49.707 に答える
36

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

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

于 2013-02-22T05:53:28.477 に答える
32

私はかなりきれいな解決策を思いつくことができました。IMOはハックが最も少なく、これは技術的には「ビットマップを描画する」ソリューションですが、少なくともフラグメント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);
}

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

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

于 2014-03-26T21:54:37.777 に答える
16

明確にするために私のソリューションを投稿しています。解決策は非常に簡単です。親のフラグメント トランザクション アニメーションを模倣しようとしている場合は、同じ長さの子フラグメント トランザクションにカスタム アニメーションを追加するだけです。ああ、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>
于 2014-01-16T00:35:50.183 に答える
7

これで問題を完全に解決できない可能性があることは理解していますが、他の人のニーズに合うかもしれません。実際には を動かしたりアニメーション化したりしないenter/exitおよびpopEnter/popExitアニメーションを子に追加できます。アニメーションの継続時間/オフセットが親アニメーションと同じである限り、親のアニメーションと一緒に移動/アニメーション化されているように見えます。FragmentFragmentFragment

于 2013-02-21T14:40:34.607 に答える
2

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

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

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

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

将来この問題を抱えている人々を助けるために、この解決策を提示したいと思いました. 元の投稿者の他の人との会話をたどって、彼が投稿したコードを見ると、元の投稿者が最終的に、親フラグメントをアニメーション化しながら、子フラグメントにノーオペレーション アニメーションを使用するという結論に達することがわかります。このソリューションは、FragmentPagerAdapter で ViewPager を使用するときに面倒なすべての子フラグメントを追跡する必要があるため、理想的ではありません。

私はあらゆる場所で子フラグメントを使用しているので、効率的でモジュラーな (簡単に削除できる) このソリューションを思いつきました。

これを実装する方法はたくさんあります。シングルトンを使用することにしました。これを 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 に追加し、破棄されると削除します。これを onAttach/Detach で行うか、シーケンス内の他の一致する方法で行うことができます。Create/Destroy View を選択した私のロジックは、Fragment に View がない場合、引き続き表示されるようにアニメーション化することを気にしないためです。このアプローチは、FragmentPagerAdapter が保持しているすべての Fragment を追跡するのではなく、3 つだけを追跡するため、Fragments を使用する 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>

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

于 2013-07-03T23:45:34.173 に答える
0

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

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

親フラグメントの削除時に子フラグメントのカスタム アニメーションが実行されない

他の人が避けてきたように、PARENT を削除する前に (子ではなく) PARENT を非表示にすることが最善の方法です。

            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 アニメーションは、独自の非同期キューで行われます。

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

于 2019-03-13T15:53:50.927 に答える