'Android transition animation does not work in API below 26

I tried to create a profile preference like WhatsApp where the profile photo is shared with the profile activity. I used the following code which works properly in Android 8 and above. But in Android 7 and below, the shared element animation does not work. This is the result of a test on API 21.

Irrelevant codes have been removed.

SettingsActivity.java :

public class SettingsActivity extends BaseActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_settings);
    setupToolbar();
    getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.pref_content, new MainPreferenceFragment())
            .commit();
}

private void setupToolbar() {
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    try {
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
}}

MainPreferenceFragment.java :

import android.transition.TransitionInflater;
import androidx.core.app.ActivityOptionsCompat;
import androidx.preference.ListPreference;
import androidx.transition.Explode;
import androidx.transition.Fade;
import androidx.transition.Transition;

public class MainPreferenceFragment extends PreferenceFragmentCompat {
public static final String KEY_PROFILE = "pref_profile_key";

private File myPhotoFile;
private int profileIconSize;
private Preference profile;

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    setPreferencesFromResource(R.xml.preferences, rootKey);
    Context context = getContext();
    initPreferences(context);
    setListeners(context);
}

@Override
public void onResume() {
    super.onResume();
    profile.setTitle(MainBroadcastReceiver.getMyDeviceName());
    if (myPhotoFile.exists()) {
        profile.setIcon(
                new BitmapDrawable(getResources(), ThumbnailUtils.extractThumbnail(
                        BitmapFactory.decodeFile(myPhotoFile.getPath()), profileIconSize, profileIconSize)));
    } else {
        profile.setIcon(R.drawable.avatar_contact);
    }
}

private void initPreferences(Context context) {

    profile = findPreference(KEY_PROFILE);

    profile.setSummary(MainBroadcastReceiver.getMyDeviceMacAddress());
    myPhotoFile = FileManager.myPhotoFile(context);
    profileIconSize = ActivityManager.dp2px(context, 67F);
}

private void setListeners(Context context) {

    profile.setOnPreferenceClickListener(preference -> {
        AppCompatActivity activity = (AppCompatActivity) getActivity();
        Intent intent = new Intent(activity, ProfileActivity.class);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            activity.getWindow().setExitTransition(TransitionInflater.from(activity).inflateTransition(R.transition.fade));
            View profileView = getListView().getChildAt(profile.getOrder());
            ImageView profileImageView = profileView.findViewById(android.R.id.icon);
            ActivityOptionsCompat options = ActivityOptionsCompat
                    .makeSceneTransitionAnimation(activity
                            , profileImageView, profileImageView.getTransitionName());
            startActivity(intent, options.toBundle());
        } else {
            startActivity(intent);
        }
        return true;
    });
}}

preference_profile.xml :

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/chat_row_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">

<LinearLayout
    android:layout_width="0dp"
    android:layout_height="1dp"
    android:background="?attr/dividerColor"
    android:orientation="horizontal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

<TextView
    android:id="@android:id/summary"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="12dp"
    android:layout_marginRight="12dp"
    android:ellipsize="end"
    android:gravity="start"
    android:maxLines="1"
    android:singleLine="true"
    android:text="Okay, Bye."
    android:textAlignment="gravity"
    android:textColor="?android:attr/textColorSecondary"
    android:textSize="14sp"
    android:transitionName="macTransition"
    app:layout_constrainedWidth="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="@android:id/title"
    app:layout_constraintTop_toBottomOf="@android:id/title" />

<TextView
    android:id="@android:id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="15dp"
    android:layout_marginLeft="15dp"
    android:ellipsize="end"
    android:gravity="start"
    android:maxLines="1"
    android:singleLine="true"
    android:text="Robert Downey"
    android:textAlignment="gravity"
    android:textColor="@android:color/black"
    android:textSize="22sp"
    android:transitionName="nameTransition"
    app:layout_constrainedWidth="true"
    app:layout_constraintBottom_toTopOf="@android:id/summary"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintHorizontal_chainStyle="spread_inside"
    app:layout_constraintStart_toEndOf="@android:id/icon"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_chainStyle="packed" />

<de.hdodenhof.circleimageview.CircleImageView
    android:id="@android:id/icon"
    android:layout_width="67dp"
    android:layout_height="67dp"
    android:layout_marginStart="14dp"
    android:layout_marginLeft="14dp"
    android:layout_marginTop="17dp"
    android:layout_marginBottom="17dp"
    android:src="@drawable/avatar_contact"
    android:transitionName="imageTransition"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

preferences.xml:

<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.preference.Preference
    android:icon="@drawable/avatar_contact"
    android:key="pref_profile_key"
    android:layout="@layout/preference_profile"
    android:summary="u:n:k:n:o:w:n"
    android:title="@string/unknown" />

</androidx.preference.PreferenceScreen>

ProfileActivity.java :

import android.transition.Transition;
import android.transition.TransitionInflater;

public class ProfileActivity extends BaseActivity {

private SimpleDraweeView imageDrawee;
private ImageView setImageImageView;

private boolean isBackPressed = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_profile);
    setupTransition();
}

private void setupTransition() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getWindow().setAllowEnterTransitionOverlap(true);
        Transition fade = TransitionInflater.from(this).inflateTransition(R.transition.fade);
        fade.excludeTarget(R.id.setImage, true);
        getWindow().setEnterTransition(fade);
        getWindow().getSharedElementEnterTransition().addListener(new Transition.TransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {

            }

            @Override
            public void onTransitionEnd(Transition transition) {
                if (!isBackPressed) {
                    setImageImageView.setVisibility(View.VISIBLE);
                    ScaleAnimation scaleUp = new ScaleAnimation(0f, 1f, 0f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    scaleUp.setDuration(150);
                    scaleUp.setFillAfter(true);
                    setImageImageView.startAnimation(scaleUp);
                }
            }

            @Override
            public void onTransitionCancel(Transition transition) {

            }

            @Override
            public void onTransitionPause(Transition transition) {

            }

            @Override
            public void onTransitionResume(Transition transition) {

            }
        });
    } else {
        setImageImageView.setVisibility(View.VISIBLE);
        ScaleAnimation scaleUp = new ScaleAnimation(0f, 1f, 0f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleUp.setDuration(150);
        scaleUp.setFillAfter(true);
        setImageImageView.startAnimation(scaleUp);
    }
}

@Override
public void onBackPressed() {
    isBackPressed = true;
    ScaleAnimation scaleUp = new ScaleAnimation(1f, 0f, 1f, 0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    scaleUp.setDuration(150);
    scaleUp.setFillAfter(true);
    scaleUp.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            setImageImageView.setVisibility(View.INVISIBLE);
            ProfileActivity.super.onBackPressed();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });
    setImageImageView.startAnimation(scaleUp);
}}

activity_profile.xml :

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/image"
    android:layout_width="160dp"
    android:layout_height="160dp"
    android:layout_marginTop="24dp"
    android:transitionName="imageTransition"
    app:actualImageScaleType="centerCrop"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/appbar"
    app:placeholderImage="@drawable/ic_settings_profile"
    app:placeholderImageScaleType="centerCrop"
    app:roundAsCircle="true" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/setImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="2dp"
    android:layout_marginRight="2dp"
    android:layout_marginBottom="2dp"
    android:clickable="true"
    android:focusable="true"
    android:src="@drawable/ic_home_camera"
    android:tint="@color/white"
    android:visibility="invisible"
    app:backgroundTint="@color/colorPrimaryLight"
    app:fabCustomSize="48dp"
    app:layout_constraintBottom_toBottomOf="@+id/image"
    app:layout_constraintEnd_toEndOf="@+id/image"
    app:rippleColor="@color/white" />

</androidx.constraintlayout.widget.ConstraintLayout>

syles.xml :

<resources>
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar" />

<style name="AppTheme" parent="AppTheme.Base">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

styles.xml (v21) :

<resources>
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowActivityTransitions">true</item>

    <item name="android:windowEnterTransition">@transition/explode</item>
    <item name="android:windowExitTransition">@transition/explode</item>

    <item name="android:windowSharedElementEnterTransition">
        @transition/change_image_transform
    </item>
    <item name="android:windowSharedElementExitTransition">
        @transition/change_image_transform
    </item>
</style>
</resources>

change_image_transition.xml :

<transitionSet>
<changeImageTransform />
<changeBounds />
</transitionSet>

explode.xml (v21) :

<explode xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
    <target android:excludeId="@android:id/statusBarBackground" />
    <target android:excludeId="@android:id/navigationBarBackground" />
    <target android:excludeId="@id/appbar" />
</targets>
</explode>

fade.xml (v21) :

<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
    <target android:excludeId="@android:id/statusBarBackground" />
    <target android:excludeId="@android:id/navigationBarBackground" />
    <target android:excludeId="@id/appbar" />
</targets>
</fade>


Solution 1:[1]

APIs below 26 simple have different stuff so you might want to consider replacing your animation with another in api 26 and below ( and other stuff but maybe not in this application in general you can google and go to andoid official website to check that. So simply your might not most of the devices which have api 26 and below. Consider testing on physical devices as well to be sure.

go here and make sure to chose 26 in api drop menu top left. https://developer.android.com/reference/android/view/animation/package-summary

good luck

Solution 2:[2]

I found the cause of this problem and the solution. The problem was not with the code I wrote in the question, but with the use of dynamic language in my BaseActivity. The dynamic language I implemented, in Android 7 and below recreated activity each time on the onResume method. And every time an activity was recreated, the transition animation did not work. To solve this problem, I disabled the dynamic language for my ProfileActivity.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Hussein Yaqoobi