Reputation: 14571
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:
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
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
Reputation: 14618
UPDATED ANSWER
enable dark theme:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
forcefully disable dark theme:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
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:
"Theme.AppCompat.DayNight"
like
<style name="DarkTheme" parent="Theme.AppCompat.DayNight">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
drawable & drawable-night,
values & values-night
Upvotes: 1
Reputation: 11
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
Upvotes: 0
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);
Upvotes: 17
Reputation: 381
here is my solution:
-> 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:
Upvotes: 5
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
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