web-dev-qa-db-ja.com

FragmentTransactionアニメーションが上にスライドします

FragmentTransaction.setCustomAnimationsを使用して次の効果を達成しようとしています。

  1. フラグメントAが表示されています
  2. フラグメントAをフラグメントBに交換します。交換中、フラグメントAは表示されたままです。フラグメントBは右からスライドします。フラグメントBは、フラグメントAの上にスライドします。

アニメーション設定でスライドを取得しても問題ありません。私の問題は、アニメーションのスライドの実行中に、フラグメントAをその場所に留めて、フラグメントBの下にする方法を理解できないことです。私が何をしても、フラグメント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();

ご覧のように、実際にフラグメントAがトランザクション中にある場所に留まる以外のことをしたくないので、「out」アニメーション用にアニメーションR.anim.nothingを定義しました。

アニメーションリソースは次のとおりです。

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" />
67
Matt Accola

私に合った解決策を見つけました。最終的に、FragmentStatePagerAdapterでViewPagerを使用しました。 ViewPagerはスワイプ動作を提供し、FragmentStatePagerAdapterはフラグメント内でスワップします。 1つのページを着信ページの「下」に表示させる効果を達成する最後のトリックは、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);
            }
        }
    }

}
8
Matt Accola

まだ答えが必要かどうかはわかりませんが、最近同じことをする必要があり、あなたが望むことをする方法を見つけました。

私はこのようなものを作りました:

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

FragmentをFrameLayoutに表示します。

それはうまく動作しますが、古いフラグメントはまだ私のビューにあります、私はAndroid私が置くなら、彼が望むようにそれを管理します:

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>
20
Meph

Lollipopから始めて、入力フラグメントのde translationZを増やすことができます。終了するものの上に表示されます。

例えば:

@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;
}
17
JFrite

さらに実験を重ねた後(これが私の2番目の答えです)、問題は、「留まる」ことを明示的に示す別のアニメーションが必要な場合、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
Merk

私はこれまでの提案よりも洗練された代替ソリューション(あまりテストされていません)を見つけました。

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クラスの内部クラス内から呼び出されています。

新しいフラグメントが挿入され、アニメーションが完了してから、古いフラグメントが削除されます。

一部のアプリケーションでは、これにメモリの問題がある場合がありますが、両方のフラグメントを無期限に保持するよりも優れています。

2
Merk

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);
0
Efi G

これは、興味のある人のための私の現在の回避策です。

新しい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();
                }
            }
        });
_

上記のHandlerではなく、ある種のAnimationListenerを好みますが、onCreateAnimation()のようなフラグメントに結び付けられていないトランザクションアニメーションの終了を検出するために使用できる方法がありませんでした。適切なリスナーとの提案/編集を歓迎します。

この方法で追加しているFragmentsは軽量であるため、フラグメントコンテナーにそれらを上にあるフラグメントと一緒に置いても問題ありません。

フラグメントを削除する場合は、fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss();HandlerRunnableOnBackStackChangedListenerに入れることができます。

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

上記の解決策はコンテナの最初のフラグメントでは機能しないことに注意してください(バックスタックにないため)。それを復元する別の方法が必要になります。おそらく最初のフラグメントへの参照を何らかの方法で保存する必要があります。 。しかし、バックスタックを使用せず、常に手動でフラグメントを置き換える場合、これは問題ではありません。 ORバックスタック(最初のものを含む)にすべてのフラグメントを追加し、onBackPressedをオーバーライドして、フラグメントが1つだけの場合に空白の画面を表示する代わりにアクティビティを終了できます。バックスタックに残った。

EDIT:上記のFragmentTransaction.remove()FragmentTransaction.add()をおそらく置き換えることができる以下の関数を発見しました。

FragmentTransaction. detach()

UIから特定のフラグメントをデタッチします。これは、バックスタックに置かれたときと同じ状態です。フラグメントはUIから削除されますが、その状態はフラグメントマネージャーによってアクティブに管理されています。この状態になると、そのビュー階層は破棄されます。

FragmentTransaction. attach()

Detach(Fragment)を使用して、UIから以前にデタッチしたフラグメントを再アタッチします。これにより、ビュー階層が再作成され、UIにアタッチされて表示されます。

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