76

I have made a few apps that support multiple themes, but I always had to restart the app when user switches theme, because setTheme() needs to be called before setContentView().

I was okay with it, until I discovered this app. It can seamlessly switch between two themes, and with transitions/animations too!

enter image description here

Please give me some hints on how this was implemented (and animations too). Thanks!

MiguelHincapieC
  • 5,445
  • 7
  • 41
  • 72
WSBT
  • 33,033
  • 18
  • 128
  • 133

6 Answers6

64

@Alexander Hanssen's answer basically has answered this... Don't know why it was not accepted... Maybe because of the finish()/startActivity(). I voted for it and I tried to comment but cannot...

Anyway, I would do exactly what he described in terms of styles.

<style name="AppThemeLight" parent="Theme.AppCompat.Light">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<!-- This will set the fade in animation on all your activities by default -->
<style name="WindowAnimationTransition">
    <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
    <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>

But instead of finish/start with new intent:

Intent intent = new Intent(this, <yourclass>.class);
startActivity(intent);
finish();

I would do:

@Override
protected void onCreate(Bundle savedInstanceState) {

    // MUST do this before super call or setContentView(...)
    // pick which theme DAY or NIGHT from settings
    setTheme(someSettings.get(PREFFERED_THEME) ? R.style.AppThemeLight : R.style.AppThemeDark);

    super.onCreate(savedInstanceState);
}

// Somewhere in your activity where the button switches the theme
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

        // decide which theme to use DAY or NIGHT and save it
        someSettings.save(PREFFERED_THEME, isDay());

        Activity.this.recreate();
    }
});

The effect is as shown in the video...

GKA
  • 1,230
  • 1
  • 15
  • 15
  • 2
    what is difference between killing the creating activity and recreating activity..Both will recreate activity..Just that animation won't visible...If I want my editText data to be there on switching theme..This code wont work.. – Android Geek Mar 30 '19 at 10:04
  • 2
    It's an old question, but i'm having issues with this solution. More precisely, everything works as expected except the animation. I also tried using a custom animation, but the animation stops after 20-30ms, regardless of the duration i set in the xml. Same happens with the stock animations you suggested. I recreate the activity using getActivity() in a fragment i use for the settings. – m.i.n.a.r. Oct 14 '19 at 17:35
  • @GKA What about the other activities? The change reflects in the same activity but do I need to apply the same theme in every activity? – Kishan Solanki Mar 27 '20 at 07:37
  • 1
    @KishanSolanki I do this in my Main activity. I think the theme applies to the whole app. – GKA Mar 28 '20 at 12:32
  • 2
    I have an issue with this solution, I use fragments in the activity. When I restart the activity, I lose data in the fragments such as the scroll list position. – Majid Sadeghi Apr 11 '20 at 12:16
  • 2
    @MajidSadeghi this is a standard android life-cycle issue. Save and restore the info you require in the activity's life-cycle hooks. – GKA Apr 12 '20 at 14:04
  • this solution assumes that dark themes and light themes have different names. But what about when the dark theme is under folder style-night with the same name? This works when app is restarted but does not respond to theme change live. – Sandeep Dixit Jun 11 '22 at 18:31
17

The transition/animation makes the theme change seamless when you restart the activity, and this can be done by adding the items "android:windowanimationStyle" to your themes, and then referencing a style where you specifiy how the Activity should animate when it enters and exits. Note that this makes the animation apply on all activities with that theme.

<style name="AppThemeLight" parent="Theme.AppCompat.Light">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat">
    <!-- Customize your theme here. -->
    <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
</style>
<!-- This will set the fade in animation on all your activities by default -->
<style name="WindowAnimationTransition">
    <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
    <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>

Then, when you want to change theme you could do this when clicking a button:

AppSettings settings = AppSettings.getInstance(this);
settings.set(AppSettings.Key.USE_DARK_THEME,
!settings.getBoolean(AppSettings.Key.USE_DARK_THEME));
Intent intent = new Intent(this, <yourclass>.class);
startActivity(intent);
finish();

Then in your onCreate method, use the setTheme() to apply the theme that is currently set in AppSettings like this:

AppSettings settings = AppSettings.getInstance(this);
setTheme(settings.getBoolean(AppSettings.Key.USE_DARK_THEME) ? R.style.AppThemeDark : R.style.AppThemeLight);
super.onCreate(savedInstanceState);
setContentView(<yourlayouthere>);

Check out this gist for reference: https://gist.github.com/alphamu/f2469c28e17b24114fe5

5

for those who are trying to find solution for android version 10 or updated.

to set dark/light mode use this:

AppCompatDelegate.setDefaultNightMode(state) //state can be AppCompatDelegate.MODE_NIGHT_YES or AppCompatDelegate.MODE_NIGHT_NO

it will change the display of your app but with a flicker

to avoid the activity recreation flicker (for smooth transition), in your activity add the below method

@Override
    public void recreate() {
        finish();
        overridePendingTransition(R.anim.anime_fade_in,
                                  R.anim.anime_fade_out);
        startActivity(getIntent());
        overridePendingTransition(R.anim.anime_fade_in,
                                  R.anim.anime_fade_out);
    }
Asad
  • 1,241
  • 3
  • 19
  • 32
  • 3
    While this might look nice for multiple-activity apps, it doesn't always work right for single-activity / multi-fragment apps. In such cases, re-creating the activity like this, does not lead the user back to where they were. – m_katsifarakis Nov 17 '21 at 22:26
4

setTheme() before super.onCreate(savedInstanceState) in GKA answer is perfect approach and work well, thanks to GKA.

but it creates new instances for all resources again, including activities, fragments, and recycler views. I think it may be heavy work and cause to loss of some saved data like local variables.

accourding to google document: https://developer.android.com/reference/android/app/Activity#recreate()

Cause this Activity to be recreated with a new instance. This results in essentially the same flow as when the Activity is created due to a configuration change -- the current instance will go through its lifecycle to onDestroy() and a new instance then created after it.

there is another approach that you can change the theme programmatically with code (Java or Kotlin), in this approach you don't need to recreate all resources, and also you can use custom animation like ripple.

check my GitHub library: https://github.com/imandolatkia/Android-Animated-Theme-Manager

in this library, you can create your custom themes and change them dynamically with ripple animation without recreating any resources.

enter image description here

Iman Dolatkia
  • 186
  • 1
  • 7
2

Simply efficient one liner in fragment:

requireActivity().recreate();

For activity:

recreate();
Ali Nawaz
  • 2,016
  • 20
  • 30
1

There isn't anything preventing you from calling setTheme() and then setContentView() again. You'll just need to restructure your app a bit so that, if you change the theme, you need to reinitialize any member variables you might have that are holding references to View objects.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • Do you think that's what the developers of the app in the video used? From the animations, I feel they probably didn't go this direction. – WSBT May 11 '15 at 17:52
  • It certainly is the easiest way to do it. I've built apps like this. As long as your layouts aren't too complicated, it isn't a big deal. The only other alternative that I can think of is to write code that walks the entire `View` tree and sets the `Theme` on each and every `View`. – David Wasser May 11 '15 at 17:59