Alin
Alin

Reputation: 14571

Day/Night theme for android app

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 ?

Upvotes: 13

Views: 32715

Answers (6)

Kishan Solanki
Kishan Solanki

Reputation: 14618

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

Upvotes: 1

navyarajeevan
navyarajeevan

Reputation: 11

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

Upvotes: 0

nirav kalola
nirav kalola

Reputation: 1220

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

Upvotes: 17

user1892410
user1892410

Reputation: 381

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

Upvotes: 5

CommonsWare
CommonsWare

Reputation: 1006614

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.

Upvotes: 19

mpellegr
mpellegr

Reputation: 3182

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.

Upvotes: 4

Related Questions