How to start shared element transition using Fragments?

前端 未结 8 1372
心在旅途
心在旅途 2020-12-04 06:32

I am trying to implement transitions between fragments which have \"shared elements\" as described in the new material design specs. The only method I can find is the Activi

相关标签:
8条回答
  • 2020-12-04 07:09

    The key is to use a custom transaction with

    transaction.addSharedElement(sharedElement, "sharedImage");
    

    Shared Element Transition Between Two Fragments

    In this example, one of two different ImageViews should be translated from the ChooserFragment to the DetailFragment.

    In the ChooserFragment layout we need the unique transitionName attributes:

    <ImageView
        android:id="@+id/image_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_first"
        android:transitionName="fistImage" />
    
    <ImageView
        android:id="@+id/image_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_second"
        android:transitionName="secondImage" />
    

    In the ChooserFragments class, we need to pass the View which was clicked and an ID to the parent Activity wich is handling the replacement of the fragments (we need the ID to know which image resource to show in the DetailFragment). How to pass information to a parent activity in detail is surely covered in another documentation.

    view.findViewById(R.id.image_first).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (mCallback != null) {
                mCallback.showDetailFragment(view, 1);
            }
        }
    });
    
    view.findViewById(R.id.image_second).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (mCallback != null) {
                mCallback.showDetailFragment(view, 2);
            }
         }
    });
    

    In the DetailFragment, the ImageView of the shared element also needs the unique transitionName attribute.

    <ImageView
        android:id="@+id/image_shared"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:transitionName="sharedImage" />
    

    In the onCreateView() method of the DetailFragment, we have to decide which image resource should be shown (if we don't do that, the shared element will disappear after the transition).

    public static DetailFragment newInstance(Bundle args) {
        DetailFragment fragment = new DetailFragment();
        fragment.setArguments(args);
        return fragment;
    }
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_detail, container, false);
    
        ImageView sharedImage = (ImageView) view.findViewById(R.id.image_shared);
    
        // Check which resource should be shown.
        int type = getArguments().getInt("type");
    
        // Show image based on the type.
        switch (type) {
            case 1:
                sharedImage.setBackgroundResource(R.drawable.ic_first);
                break;
    
            case 2:
                sharedImage.setBackgroundResource(R.drawable.ic_second);
                break;
        }
    
        return view;
    }
    

    The parent Activity is receiving the callbacks and handles the replacement of the fragments.

    @Override
    public void showDetailFragment(View sharedElement, int type) {
        // Get the chooser fragment, which is shown in the moment.
        Fragment chooserFragment = getFragmentManager().findFragmentById(R.id.fragment_container);
    
        // Set up the DetailFragment and put the type as argument.
        Bundle args = new Bundle();
        args.putInt("type", type);
        Fragment fragment = DetailFragment.newInstance(args);
    
        // Set up the transaction.
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
    
        // Define the shared element transition.
        fragment.setSharedElementEnterTransition(new DetailsTransition());
        fragment.setSharedElementReturnTransition(new DetailsTransition());
    
        // The rest of the views are just fading in/out.
        fragment.setEnterTransition(new Fade());
        chooserFragment.setExitTransition(new Fade());
    
        // Now use the image's view and the target transitionName to define the shared element.
        transaction.addSharedElement(sharedElement, "sharedImage");
    
        // Replace the fragment.
        transaction.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName());
    
        // Enable back navigation with shared element transitions.
        transaction.addToBackStack(fragment.getClass().getSimpleName());
    
        // Finally press play.
        transaction.commit();
    }
    

    Not to forget - the Transition itself. This example moves and scales the shared element.

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class DetailsTransition extends TransitionSet {
    
        public DetailsTransition() {
            setOrdering(ORDERING_TOGETHER);
            addTransition(new ChangeBounds()).
                addTransition(new ChangeTransform()).
                addTransition(new ChangeImageTransform());
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 07:11

    Following are some helpful resources:

    https://github.com/lgvalle/Material-Animations

    http://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html

    https://www.youtube.com/watch?v=5e1Yh0fSZhQ

    0 讨论(0)
  • 2020-12-04 07:13

    How to start shared element transition using Fragments?

    I assume you want to transit your Image using Fragment (not using Activity)

    Disclamer: it wont work perfectly if you have already set AppTheme

    And please, keep the source transitionName and destination transitionName same

    You have to do three things for transition:

    1.Set transitionName to the source View -> in xml or programatically before calling makeFragmentTransition

    private void setImageZoom(boolean isImageZoom) {
    ImageView imageView = this.findViewById(R.id.image);
        if (isImageZoom) {
            imageView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ViewCompat.setTransitionName(imageView, "imageTransition");
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        makeFragmentTransition(imageView);
                }
                }
            });
        }
    }
    

    2.Fragment Transition

    Set TransitionSet for the specicific Transition animation

    apply them on Fragment

    call addSharedElement(View, transitionName) while fragmentTransition

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public void makeFragmentTransition(ImageView sourceTransitionView) {
        //transtionName for sourceView  
        //MUST set transitionName before calling this method(programattically or give ->transitionName to the view in xml)
        String sourceTransitionName = ViewCompat.getTransitionName(sourceTransitionView);
    
        TransitionSet transitionSet = new TransitionSet();
        transitionSet.setDuration(500);
        transitionSet.addTransition(new ChangeBounds());  //to expand boundaries
        transitionSet.addTransition(new ChangeTransform()); //for transtion vertically
        transitionSet.addTransition(new ChangeImageTransform()); // image transform work 
        transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);  // all three work together to look one task
    
        ImageTransitionFragment fragment = new ImageTransitionFragment();
        fragment.setSharedElementEnterTransition(transitionSet);
        fragment.setSharedElementReturnTransition(transitionSet);
        fragment.setAllowReturnTransitionOverlap(false);
    
        try {
    
            getHostActivity().getSupportFragmentManager()
                    .beginTransaction()
                    //sharedElement is set here for fragment 
                    //it will throw exception if transitionName is not same for source and destionationView
                    .addSharedElement(sourceTransitionView, sourceTransitionName)
                    //R.id.fragmentView is the View in activity on which fragment will load...
                    .replace(R.id.fragmentView, fragment)
                    .addToBackStack(null)
                    .commit();
        } catch (Exception e) {
            //
            String string = e.toString();
        }
    }
    

    3.Last Set destionation transition Name with sourceTransitionName -> imageTransition

    so add transitionName in ImageView of ImageTransitionFragment

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/destionationTransitionPage"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:transitionName="@string/pageTransition"
    android:background="@color/black_color">
    
    
    <com.android.foundation.ui.component.FNImageView
        android:id="@+id/destinationImageView"
        android:layout_width="@dimen/_400dp"
        android:layout_gravity="center"
        android:transitionName="imageTransition"
        android:layout_height="@dimen/_400dp" />
    

    Please respond if anything is not clear or it need more improvement

    0 讨论(0)
  • 2020-12-04 07:20

    I searched for SharedElement in fragments and I find very useful source code on GitHub.

    1.first you should define transitionName for your Objects(Like ImageView) in both Fragments layout(We add a button in fragment A for handling click event):

    fragment A:

      <ImageView
        android:id="@+id/fragment_a_imageView"
        android:layout_width="128dp"
        android:layout_height="96dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="80dp"
        android:scaleType="centerCrop"
        android:src="@drawable/gorilla"
        android:transitionName="@string/simple_fragment_transition />
    
    <Button
        android:id="@+id/fragment_a_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="24dp"
        android:text="@string/gorilla" />
    

    fragment B:

        <ImageView
        android:id="@+id/fragment_b_image"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:scaleType="centerCrop"
        android:src="@drawable/gorilla"
        android:transitionName="@string/simple_fragment_transition" />
    
    1. Then you should write this code in your transition file in transition Directory(if you haven't this Directory so create One: res > new > Android Resource Directory > Resource Type = transition > name = change_image_transform ):

    change_image_transform.xml:

     <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
      <changeBounds/>
      <changeTransform/>
      <changeClipBounds/>
      <changeImageTransform/>
    </transitionSet>
    
    1. In the last step you should complete codes in java:

    fragment A:

    public class FragmentA extends Fragment {
    
        public static final String TAG = FragmentA.class.getSimpleName();
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_a, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            final ImageView imageView = (ImageView) view.findViewById(R.id.fragment_a_imageView);
            Button button = (Button) view.findViewById(R.id.fragment_a_btn);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    getFragmentManager()
                            .beginTransaction()
                            .addSharedElement(imageView, ViewCompat.getTransitionName(imageView))
                            .addToBackStack(TAG)
                            .replace(R.id.content, new FragmentB())
                            .commit();
                }
            });
        }
    }
    

    fragment B:

    public class FragmentB extends Fragment {
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
    
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_b, container, false);
        }
    }
    

    don't forget to show your "A" fragment in your activity:

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.content, new SimpleFragmentA())
                    .commit();
        }
    

    source : https://github.com/mikescamell/shared-element-transitions

    0 讨论(0)
  • 2020-12-04 07:21

    Shared elements do work with Fragments but there are some things to keep in mind:

    1. Don't try to set the sharedElementsTransition in the onCreateView of your Fragment. You have to define them when creating an instance of your Fragment or in onCreate.

    2. Take note of the official documentation on the possible animations for enter/exit transitions & sharedElementTransition. They are not the same.

    3. Trial and error :)

    0 讨论(0)
  • 2020-12-04 07:24

    I had the same problem but had it working by adding a new fragment from another fragment. The following link is very helpful in getting started on this: https://developer.android.com/training/material/animations.html#Transitions

    Following is my code that works. I'm animating an ImageView from one fragment to the other. Make sure the View you want to animate has the same android:transitionName in both fragments. The other content doesn't really matter.

    As a test, you could copy this to both your layout xml files. Make sure the image exists.

    <ImageView
    android:transitionName="MyTransition"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:src="@drawable/test_image" />
    

    Then I have 1 file in my res/transition folder, named change_image_transform.xml.

    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
        <changeImageTransform />
    </transitionSet>
    

    Now you can get started. Lets say you have Fragment A containing the image and want to add Fragment B.

    Run this in Fragment A:

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
            case R.id.product_detail_image_click_area:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                    setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
    
                    // Create new fragment to add (Fragment B)
                    Fragment fragment = new ImageFragment();
                    fragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                    fragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
    
                    // Our shared element (in Fragment A)
                    mProductImage   = (ImageView) mLayout.findViewById(R.id.product_detail_image);
    
                    // Add Fragment B
                    FragmentTransaction ft = getFragmentManager().beginTransaction()
                            .replace(R.id.container, fragment)
                            .addToBackStack("transaction")
                            .addSharedElement(mProductImage, "MyTransition");
                    ft.commit();
                }
                else {
                    // Code to run on older devices
                }
                break;
        }
    }
    
    0 讨论(0)
提交回复
热议问题