【问题标题】:Day/Night theme for android app安卓应用的日/夜主题
【发布时间】:2013-08-01 18:15:34
【问题描述】:

我有一个应用程序,我需要在其中实现日/夜主题。不幸的是,没有简单的方法通过使用样式来制作主题,我需要能够更新:布局背景、按钮选择器、文本颜色、文本大小、图像、图标、动画。

据我所知,我有两个选择:

  1. 对于夜间/白天有不同的 xml 布局文件,例如 home_day.xml / home_night.xml。应用程序中大约有 30 个屏幕,因此最终将有 60 个 xml 布局。在活动/片段 onCreate 上,基于当前时间我可以setContentView。这会增加一些 xml 文件,但避免在活动中添加更多代码

  2. 只有一个日/夜布局,在活动的onCreatefindviewById 上,我想为每个项目设置主题并根据当前日/夜更新他的属性。这可能会为许多视图生成大量额外代码、查找视图和应用属性。

我的目标是 2。但我愿意接受您的任何建议。那么,你会选择什么?为什么?

【问题讨论】:

  • 我会使用 -night 作为夜间模式的资源集限定符,将你的夜间特定资源放在那里。
  • 这真的归结为您是否介意在代码中进行 GUI 工作。就个人而言,我认为晚上有一个单独的布局,白天有另一个布局会更干净、更容易。可能还有第三种选择,但您可以在其中使用可绘制对象作为背景、文本颜色以及其他任何应该更改的内容,并为夜间/白天创建自定义状态。然后您可以循环浏览所有视图并更改状态。不知道这与您的选项 1 有何不同。
  • @CommonsWare 你能详细说明一下答案吗?谢谢

标签: android android-theme


【解决方案1】:

我会使用 -night 作为夜间模式的资源集限定符,将你的夜间特定资源放在那里。

Android 已有 the notion of night mode,可根据一天中的时间和传感器在夜间和白天模式之间切换。因此,您可以考虑使用它。

例如,要根据模式创建不同的主题,请创建 res/values/styles.xmlres/values-night/styles.xml。在每个文件中都有一个同名的主题(例如,AppTheme),但要根据您想要的白天和夜间模式之间的差异来定制主题。当您通过名称(例如,在清单中)引用您的主题时,Android 将自动加载正确的资源,如果在这些 Activity 运行时模式发生变化,Android 将自动销毁并重新创建您的 Activity。

现在,如果您希望用户手动控制是否使用以夜间为主题的 UI,-night 将无济于事。

【讨论】:

  • 感谢您的回复。我需要能够在夜间主题出现的那一刻设置代码。夏天可以是晚上 22 点,冬天可以是晚上 19​​ 点。所以我猜-night 是不可能的,因为设置夜间模式会说`更改夜间模式仅在设备上启用汽车或桌面模式时才有效。`
  • @Alin 您可以使用 UiModeManager.enableCarMode() 自己启用汽车模式,尽管我会说将设备置于汽车模式的效果对我来说不是 100% 清楚。
  • 从支持库 23.2 开始,有一些 API 用于在您的应用中包含一个主题,该主题会根据一天中的时间自动切换到夜晚。 android-developers.blogspot.com/2016/02/…
  • 您好先生,很抱歉打扰了这个帖子,但是您能和我分享一下如何制作夜间模式。非常感谢!
  • @Zuhrain:对不起,我不知道你所说的“夜间模式”是什么意思。我建议你问一个单独的 Stack Overflow 问题,在那里你可以更详细地解释你需要什么。
【解决方案2】:

查看本教程以获取完整的分步示例:click here

使用 Appcompat v23.2 支持库添加自动切换昼夜主题

在您的 build.gradle 文件中添加以下行

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

styles.xml 中制作您的主题风格,如下所示

<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>

现在添加以下行代码onCreate() 为整个应用设置主题的方法。

用于设置默认自动切换夜间模式使用

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);

用于设置默认夜间模式使用

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

用于设置默认日间模式

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

【讨论】:

  • 添加 MODE_NIGHT_FOLLOW_SYSTEM(默认)。此设置遵循系统设置,仅适用于Android P
  • 如何适应两种背景颜色? &lt;item name="android:textColorPrimary"&gt;@color/textColorPrimary&lt;/item&gt;
  • 链接:blog.nkdroidsolutions.com/… 好像坏了。
【解决方案3】:

这是我的解决方案:

  • 我想拥有自动昼/夜功能,但不必在 android 中启用繁琐的汽车模式。

-> 在给定特定位置和日期的情况下,可以从 NOAA 网页中找到计算地平线上太阳高度的算法。

--> 使用这些算法,我创建了一个计算太阳在地平线上的高度的方法,给定两个纬度和经度以及一个日历

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;
    } 
}

然后在 MainActivity 我有一个方法,每 5 分钟检查一次给定位置的太阳高度:

 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();
        }
    }

此方法设置要使用的当前样式,我有其中的三个。两个代表白天和夜晚,一个代表黄昏,此时阳光开始折射到大气中

<!-- 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>

这一直运作良好,并考虑到夏令时校正。

来源:

NOAA Sunrise Sunset

Julian Day

【讨论】:

  • 你可以使用时区
  • 最好使用时区而不是询问位置。
【解决方案4】:

实际上,您似乎也可以使用主题来描述自定义可绘制对象。看看:How to switch between night-mode and day-mode themes on Android?。您可以使用样式块创建主题,然后在 xml 布局中使用 ?attr 在主题中指定某些内容。然后您应该能够在下一个活动中调用 setTheme(R.styles.DAY_THEME) 并且所有内容都应该更新。

【讨论】:

  • 我用这种方法看到的问题,例如&lt;item name="menuIconCamera"&gt; 我相信是主题的一部分,所以我可以自定义它。但是,例如,当我需要为 imgLogo 使用不同的可绘制对象时,我该怎么办?
  • 那些不是 android 系统属性,它们是自定义的。你需要自己做这些。这是一个很好的例子:stackoverflow.com/questions/3441396/defining-custom-attrs/….
  • 顺便说一句,您还可以直接在 xml 布局中使用这些自定义样式属性。就像您创建了“exit_button_background”属性一样,您可以在 xml 中设置该值,例如“myapp:exit_button_background="@drawable/night_background"”,但对于您的情况,您似乎需要创建自定义属性以便您可以在自定义的昼夜主题中使用它们。
  • 是的,它以这种方式工作,定义自定义属性并在样式中使用它们。非常感谢您的帮助。
【解决方案5】:

更新答案

  1. 启用深色主题:

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
    
  2. 强制禁用深色主题:

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
    
  3. 根据暗模式的移动设置设置应用程序主题,即如果启用暗模式,则主题将设置为暗主题,否则 默认主题,但这仅适用于 version >= Android version Q

    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
    

注意事项:

  1. 您的应用/活动的基本主题应该是

“Theme.AppCompat.DayNight”

喜欢

<style name="DarkTheme" parent="Theme.AppCompat.DayNight">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>
  1. 您的 res 文件夹的名称将以 -night 结尾,以便您可以为白天和夜间主题设置不同的颜色和图像,例如

可绘制和可绘制的夜晚,
价值观与价值观-之夜

【讨论】:

    【解决方案6】:
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
    

    【讨论】:

    • 请在回答问题时尽量说得更清楚和解释清楚。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-27
    • 2010-12-18
    相关资源
    最近更新 更多