13

I have an app in which I need to implement day / night theme. Unfortunately there is no simple way of making theming by just using styles, I need to be able to update: layout backgrounds, button selectors, text color, text size, images, icons, animations.

From what I see I have 2 options:

  1. Have different xml layout files for night/day, so something like home_day.xml / home_night.xml. There are around 30 screens in the app, so in the end there will be 60 xml layouts. On activity/fragment onCreate, based on current hour I could setContentView. This adds some more xml files but avoids adding more code in activities

  2. Have only one layout for day/night and on activity's onCreate findviewById for each item I want to theme and update his attributes based on current day/night. This could generate a lot of extra code, findviews and apply attributes for many views.

I am aiming for for 2. but I am open to any suggestions from you. So, what would you choose and why ?

Alin
  • 14,809
  • 40
  • 129
  • 218
  • 1
    I'd use `-night` as a resource set qualifier for night mode, putting your night-specific resources in there. – CommonsWare Aug 01 '13 at 18:21
  • This really just boils down to whether or not you mind doing GUI work in code. Personally, I think it would be cleaner and easier to have a separate layout for night and another one for day. There might be a third option though where you could use a drawable for your backgrounds, text color, and whatever else should change and create a custom state for night/day. Then you could cycle through all your views and change the state. Don't know how different that would be from your option 1 though. – mpellegr Aug 01 '13 at 18:25
  • 1
    @CommonsWare could you elaborate a bit in an answer ? Thanks – Alin Aug 01 '13 at 18:33

6 Answers6

19

I'd use -night as a resource set qualifier for night mode, putting your night-specific resources in there.

Android already has the notion of night mode, switching between night and daytime modes based upon time of day and sensors. Hence, you might consider using it.

For example, to have a different theme based on mode, create res/values/styles.xml and res/values-night/styles.xml. Have a theme with the same name in each file (e.g., AppTheme), but tailor the theme based upon whatever differences you want have between day and night modes. When you reference your theme by name (e.g., in the manifest), Android will automatically load in the right resources, and Android will automatically destroy and recreate your activities if the mode changes while those activities are running.

Now, if you want manual user control over whether to use a night-themed UI, -night will not help.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for the response. I need to be able to set in code the moment the night theme comes up. In the summer it can be 22pm while at winter it can be 19pm. So I guess `-night` is out of the question as setting the night mode says ` Changes to the night mode are only effective when the car or desk mode is enabled on a device.` – Alin Aug 02 '13 at 07:15
  • 1
    @Alin You can enable car mode yourself using UiModeManager.enableCarMode(), although I will say that the effects of putting a device into car mode aren't 100% clear to me. – spaaarky21 Feb 28 '14 at 00:18
  • 2
    As of support library 23.2 there are APIs for including a theme in your app that automatically switches to night based on the time of day. http://android-developers.blogspot.com/2016/02/android-support-library-232.html – Damien Diehl Mar 19 '16 at 18:31
  • Hello sir, sorry for bumping this thread, but can you share with me how to make the night mode. Thank you so much! – Ticherhaz FreePalestine Oct 08 '18 at 13:36
  • 1
    @Zuhrain: Sorry, but I do not know what you mean by "make the night mode". I suggest that you ask a separate Stack Overflow question, where you can explain in greater detail what you need. – CommonsWare Oct 08 '18 at 21:42
  • Hi, it does not work when we set the color from code? it always takes the color from light resources – hallz12 May 02 '19 at 04:32
17

Check this tutorial for a complete step by step example: click here

Add Auto Switching DayNight Theme using Appcompat v23.2 support library

Add following line in your build.gradle file

compile 'com.android.support:appcompat-v7:23.2.0'

Make your theme style in styles.xml as below

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:textColorPrimary">@color/textColorPrimary</item>
        <item name="android:textColorSecondary">@color/textColorSecondary</item>
    </style>
</resources>

Now add the following line code onCreate() method for setting theme for entire app.

For Setting Default Auto Switching Night Mode use

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);

For Setting Default Night Mode use

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

For Setting Default Day Mode use

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

enter image description here

Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
nirav kalola
  • 1,220
  • 13
  • 17
  • 1
    Add MODE_NIGHT_FOLLOW_SYSTEM (default). This setting follows the system’s setting, Only work with Android P – Codelaby Nov 17 '18 at 09:44
  • 1
    How is this accommodating for both background colors? `@color/textColorPrimary` – behelit May 19 '19 at 07:33
  • 4
    The link: http://blog.nkdroidsolutions.com/android-daynight-theme-example-using-appcompat-v23-2/ seems to be broken. – Bruno Bieri Aug 15 '19 at 10:04
5

here is my solution:

  • I wanted to have automatic day/night features but not have to enable the cumbersome car mode in android.

-> From NOAA webpages one can find algorithms to calculate the sun height over the horizon given a certain position and date.

--> Using these algoritms I created a method that calculate the sun's height over the horizon given two doubles Latitude & Longitude and a Calendar

public class SolarCalculations {

    /**
     * Calculate height of the sun above horizon for a given position and date
     * @param lat Positive to N
     * @param lon Positive to E
     * @param cal Calendar containing current time, date, timezone, daylight time savings
     * @return height of the sun in degrees, positive if above the horizon
     */
    public static double CalculateSunHeight(double lat, double lon, Calendar cal){

        double adjustedTimeZone = cal.getTimeZone().getRawOffset()/3600000 + cal.getTimeZone().getDSTSavings()/3600000;

        double timeOfDay = (cal.get(Calendar.HOUR_OF_DAY) * 3600 + cal.get(Calendar.MINUTE) * 60 + cal.get(Calendar.SECOND))/(double)86400;

        double julianDay = dateToJulian(cal.getTime()) - adjustedTimeZone/24;

        double julianCentury = (julianDay-2451545)/36525;

        double geomMeanLongSun = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360;

        double geomMeanAnomSun = 357.52911+julianCentury*(35999.05029 - 0.0001537*julianCentury);

        double eccentEarthOrbit = 0.016708634-julianCentury*(0.000042037+0.0000001267*julianCentury);

        double sunEqOfCtr = Math.sin(Math.toRadians(geomMeanAnomSun))*(1.914602-julianCentury*(0.004817+0.000014*julianCentury))+Math.sin(Math.toRadians(2*geomMeanAnomSun))*(0.019993-0.000101*julianCentury)+Math.sin(Math.toRadians(3*geomMeanAnomSun))*0.000289;

        double sunTrueLong = geomMeanLongSun + sunEqOfCtr;

        double sunAppLong = sunTrueLong-0.00569-0.00478*Math.sin(Math.toRadians(125.04-1934.136*julianCentury));

        double meanObliqEcliptic = 23+(26+((21.448-julianCentury*(46.815+julianCentury*(0.00059-julianCentury*0.001813))))/60)/60;

        double obliqueCorr = meanObliqEcliptic+0.00256*Math.cos(Math.toRadians(125.04-1934.136*julianCentury));

        double sunDeclin = Math.toDegrees(Math.asin(Math.sin(Math.toRadians(obliqueCorr))*Math.sin(Math.toRadians(sunAppLong))));

        double varY = Math.tan(Math.toRadians(obliqueCorr/2))*Math.tan(Math.toRadians(obliqueCorr/2));

        double eqOfTime = 4*Math.toDegrees(varY*Math.sin(2*Math.toRadians(geomMeanLongSun))-2*eccentEarthOrbit*Math.sin(Math.toRadians(geomMeanAnomSun))+4*eccentEarthOrbit*varY*Math.sin(Math.toRadians(geomMeanAnomSun))*Math.cos(2*Math.toRadians(geomMeanLongSun))-0.5*varY*varY*Math.sin(4*Math.toRadians(geomMeanLongSun))-1.25*eccentEarthOrbit*eccentEarthOrbit*Math.sin(2*Math.toRadians(geomMeanAnomSun)));

        double trueSolarTime = (timeOfDay*1440+eqOfTime+4*lon-60*adjustedTimeZone) % 1440;

        double hourAngle;
        if(trueSolarTime/4<0)
            hourAngle = trueSolarTime/4+180;
        else
            hourAngle = trueSolarTime/4-180;

        double solarZenithAngle = Math.toDegrees(Math.acos(Math.sin(Math.toRadians(lat))*Math.sin(Math.toRadians(sunDeclin))+Math.cos(Math.toRadians(lat))*Math.cos(Math.toRadians(sunDeclin))*Math.cos(Math.toRadians(hourAngle))));

        double solarElevation = 90 - solarZenithAngle;

        double athmosphericRefraction;
        if(solarElevation>85)
            athmosphericRefraction = 0;
        else if(solarElevation>5)
            athmosphericRefraction = 58.1/Math.tan(Math.toRadians(solarElevation))-0.07/Math.pow(Math.tan(Math.toRadians(solarElevation)),3)+0.000086/Math.pow(Math.tan(Math.toRadians(solarElevation)),5);
        else if(solarElevation>-0.575)
            athmosphericRefraction = 1735+solarElevation*(-518.2+solarElevation*(103.4+solarElevation*(-12.79+solarElevation*0.711)));
        else
            athmosphericRefraction = -20.772/Math.tan(Math.toRadians(solarElevation));
        athmosphericRefraction /= 3600;

        double solarElevationCorrected = solarElevation + athmosphericRefraction;

        return solarElevationCorrected;

    }


    /**
     * Return Julian day from date
     * @param date
     * @return
     */
    public static double dateToJulian(Date date) {

        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(date);

        int a = (14-(calendar.get(Calendar.MONTH)+1))/12;
        int y = calendar.get(Calendar.YEAR) + 4800 - a;

        int m =  (calendar.get(Calendar.MONTH)+1) + 12*a;
        m -= 3;

        double jdn = calendar.get(Calendar.DAY_OF_MONTH) + (153.0*m + 2.0)/5.0 + 365.0*y + y/4.0 - y/100.0 + y/400.0 - 32045.0 + calendar.get(Calendar.HOUR_OF_DAY) / 24 + calendar.get(Calendar.MINUTE)/1440 + calendar.get(Calendar.SECOND)/86400;

        return jdn;
    } 
}

Then in the MainActivity I have a method that checks every 5 minutes for the sun's height at the given position:

 if(displayMode.equals("auto")){
        double sunHeight = SolarCalculations.CalculateSunHeight(lat, lon, cal);
        if(sunHeight > 0 && mThemeId != R.style.AppTheme_Daylight)
        {//daylight mode
            mThemeId = R.style.AppTheme_Daylight;   
            this.recreate();
        }
        else if (sunHeight < 0 && sunHeight >= -6 && mThemeId != R.style.AppTheme_Dusk)
        {//civil dusk
            mThemeId = R.style.AppTheme_Dusk;
            this.recreate();
        }
        else if(sunHeight < -6 && mThemeId != R.style.AppTheme_Night)
        {//night mode
            mThemeId = R.style.AppTheme_Night;
            this.recreate();
        }
    }

This methods sets the current style to be used and I have three of them. Two for daylight and night, one for dusk when sunlight begins to be refracted into the atmosphere

<!-- Application theme. -->
<style name="AppTheme.Daylight" parent="AppBaseTheme">
    <item name="android:background">@color/white</item>
    <item name="android:panelBackground">@color/gray</item>
    <item name="android:textColor">@color/black</item>
</style>

<style name="AppTheme.Dusk" parent="AppBaseTheme">
    <item name="android:background">@color/black</item>
    <item name="android:panelBackground">@color/gray</item>
    <item name="android:textColor">@color/salmon</item>
</style>

<style name="AppTheme.Night" parent="AppBaseTheme">
    <item name="android:background">@color/black</item>
    <item name="android:panelBackground">@color/gray</item>
    <item name="android:textColor">@color/red</item>
</style>

This has been working pretty well and take into account daylight saving time correction.

Sources:

NOAA Sunrise Sunset

Julian Day

user1892410
  • 381
  • 5
  • 12
4

Actually it seems you can use themes to describe custom drawables as well. Take a look at: How to switch between night-mode and day-mode themes on Android?. You create your themes by using a style block and then in your xml layout you specify something in your theme by using ?attr. Then you should be able to call setTheme(R.styles.DAY_THEME) on the next activity and everything should be updated.

Community
  • 1
  • 1
mpellegr
  • 3,072
  • 3
  • 22
  • 36
  • The problem I'm seeing with this approach, for instance `` I believe is part of the theme, so I can customize it. But what in my case when I need to have a different drawable for imgLogo for instance ? – Alin Aug 01 '13 at 19:15
  • Those aren't android system attributes, they're custom. You need to make those yourself. Here's a good example of how to: http://stackoverflow.com/questions/3441396/defining-custom-attrs/3441986#3441986. – mpellegr Aug 01 '13 at 20:08
  • As an aside, you can also use these custom style attributes directly in an xml layout. Like if you created an "exit_button_background" attribute, you could set that value in your xml like "myapp:exit_button_background="@drawable/night_background"", but for your case it seems you'd need to create your custom attributes just so you could use them in a custom night and day theme. – mpellegr Aug 01 '13 at 20:13
  • Yes, in this way it works, defining custom attributes and use them in styles. Thanks a lot for your help. – Alin Aug 02 '13 at 08:56
1

UPDATED ANSWER

  1. enable dark theme:

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
    
  2. forcefully disable dark theme:

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
    
  3. set app theme based on mobile settings of dark mode, i.e. if dark mode is enabled then the theme will be set to a dark theme, if not then the default theme, but this will only work in version >= Android version Q

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
    

Notes:

  1. Your base theme for app/activity should be

"Theme.AppCompat.DayNight"

like

<style name="DarkTheme" parent="Theme.AppCompat.DayNight">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>
  1. Your res folder's names would end with -night so that different colors and images you can set for day and night themes like

drawable & drawable-night,
values & values-night

Kishan Solanki
  • 13,761
  • 4
  • 85
  • 82
0
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
navyarajeevan
  • 11
  • 1
  • 1