如何为应用程序设置主题,以避免错误的颜色过渡?

时间:2014-04-20 15:42:26

标签: android-theme android

背景

我正在"app manager" app开发主题选择器功能,并且我已成功为每个活动动态设置主题。

再次:这不是关于为活动设置主题。这实际上适合我。

问题

活动显示正确的主题,但启动应用程序时应用程序本身显示错误的主题,无论我做什么。

这是一个问题,因为当用户打开应用程序时,他会看到应用程序主题的背景,只有片刻之后,活动才会显示用户选择的主题。

因此,如果应用程序具有白色背景,并且用户选择了具有黑色背景的主题,则顺序为:

应用程序显示白色背景 - >活动正在开始并显示黑色背景。

截图:

enter image description here

所以这是错的。在这种情况下,我需要它来显示黑色到黑色的背景。

仅当用户选择了基于Holo-light的主题(默认情况下该应用程序具有该主题)时,它才能正常工作,因为颜色与打开应用程序时右侧显示的活动颜色相匹配。

我尝试了什么

我有一个想法是将应用程序的主题设置为空的一切,希望不会显示任何过渡,使用类似的东西:

<application
    ...
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >

事实上,这里的一些人提出了类似的解决方案。

这样可以,但它会导致糟糕的体验,因为在某些设备上显示第一个活动需要一些时间,因此,用户有很长时间没有看到任何内容,就好像应用程序一样没有被推出。

问题

我该如何解决这个问题?

我已经尝试在从应用程序扩展的类中设置主题,但它不会做任何事情,无论我在哪个类中调用它。

4 个答案:

答案 0 :(得分:9)

带有淡入动画的透明应用程序主题

我最初的建议是使用透明全屏应用主题(没有操作栏)。

结合这一点,我总是建议从应用程序主题到活动主题的淡入淡出的alpha动画。这可以防止在操作栏出现时对用户造成震动。

除了更改清单主题,并在某些基本活动类的onCreate()方法中添加alpha动画,OP的代码几乎完全相同,如下例所示:


清单主题定义为:

android:theme="@android:style/Theme.Translucent.NoTitleBar"

基本活动onCreate()方法:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    // set your custom theme here before setting layout
    super.setTheme(android.R.style.Theme_Holo_Light_DarkActionBar);

    setContentView(R.layout.activity_main);

    overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
}

基本淡出:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />

基本淡出(不是真的需要,但为了完整性):

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

当然,这里的动画持续时间比你投入生产的时间长 - 它们很长,所以你可以在开发阶段看到它们。


更新#1:

随后在@EmanuelMoecklin的评论中注意到,@ androiddeveloper认为这是考虑到的。它也包含在answer by dentex中。但是,正如OP所述,特别是在旧设备上的弱点是用户在尝试启动应用程序时没有获得反馈。该应用程序似乎需要很长时间才能启动。

在KitKat上,情况并非如此,因为状态栏&amp;软键从透明变为黑色,而屏幕的其余部分仍然是透明的。

采用这种方法的另一种方法是使用全屏黑色背景作为应用程序主题。这是BitspinTimely所做的事情,显然是基于该应用中令人惊叹的用户界面而被Google收购的。因此,在许多情况下,这种方法似乎是可以接受的。


更新#2:

为了加快对发布的看法,普通黑色主题的替代方案是在中心使用带有应用程序徽标的全屏图像 - &#34;闪屏&#34;样式。一旦推出,它就会逐渐消失。

透明主题无法使用透明的全屏图像。 Android忽略图像的透明度(或将透明图像叠加到黑色背景上)。 OP在评论中指出了这一点。

我们既可以有没有图像的透明主题,也可以有带图像的不透明主题(也许是另一个问题的有趣主题)。


关于使用Manifest别名的说明

Another suggestion by @sergio91pt是为清单中的不同活动使用别名。

虽然在某些情况下这可能是一种有用的技术,但在这种情况下它有一些缺点:

  1. 当主启动器别名发生更改时,即每次用户更改主题时,用户为活动创建的任何HOME屏幕快捷方式都将停止工作。
  2. 某些设备/发射器的激活速度非常慢。停用不同的别名。根据我的经验,这可能需要几秒钟(Galaxy Nexus 4.1 iirc),在此期间您要么没有可见的启动图标,要么您有2个图标。
  3. 每个可能的主题需要不同的别名 - 如果有许多不同的主题,这可能会很麻烦。

答案 1 :(得分:9)

有点晚了,但这可能就是答案。我偶然发现了它。

没有入场活动,没有自定义动画,没有黑客入侵。只是主题中的一个属性。 Android深入其内部资源。

将以下属性添加到您的应用主题:

<!--
  ~ From Theme.NoDisplay, this disables the empty preview window probably
  ~ with an incorrect theme.
  -->
<item name="android:windowDisablePreview">true</item>

你已经完成了。享受吧!

答案 2 :(得分:2)

要在应用程序开始时修复任何闪烁(操作栏,标题......),我已进入清单

android:theme="@android:style/Theme.NoTitleBar"

用于我的两个主要活动(标签容器和设置活动,我根据全息黑暗和光线切换主题)

如果您使用某些&#34;启动器活动&#34;或&#34;泼水活动&#34;同时为他们申请Theme.NoTitleBar,然后:

为每项活动宣布Theme.NoTitleBaronCreate您必须:

  1. 使用setTitle(...)和THEN

  2. 正确设置标题
  3. setTheme(R.style.CustomAppTheme)之前使用setContentView(...)设置主题 (你已经这样做了);

  4. 这样可以防止切换主题时动作栏/标题的闪烁(如果已完成&#34;即时&#34;)以及应用程序启动时。

    如果您想要自定义操作栏外观,这意味着默认的holo操作栏在您的之前不会闪烁。

答案 3 :(得分:1)

从清单上的活动主题(或未设置的应用程序)中检索转换颜色。

目前解决此限制的唯一方法是为每个真实的Activity创建一个虚拟子类,例如。 MyActivityLight,宣布一个不同的主题。活动别名不起作用,该属性将被忽略。

对于使用IntentFilter的活动,您应该只使用PackageManager#setComponentEnabledSetting()维护每个“类型”中的一个。请注意,更改可能需要几秒钟。

对于按类名启动的活动,您可以根据用户的主题推断出正确的前缀。


因此,假设您有2个主题:AppTheme.DarkAppTheme.Light以及一些活动。 黑暗主题是默认主题。

原始清单:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">
    <application android:theme="@style/AppTheme.Dark">
        <activity 
                android:name=".PrivateActivity" 
                android:exported="false" />

        <activity android:name=".ShowActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
    </application>
</manifest>

将上述所有活动更改为抽象类,并创建以LightDark为后缀的虚拟子类。

然后清单应该像这样改变:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">

    <!-- No application theme -->
    <application>
        <activity android:name=".PrivateActivityDark" 
            android:theme="@style/AppTheme.Dark"
            android:exported="false" />
        <activity android:name=".PrivateActivityLight" 
            android:theme="@style/AppTheme.Light"
            android:exported="false"
            android:enabled="false" />

        <activity 
            android:name=".ShowActivityDark"
            android:theme="@style/AppTheme.Dark">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
        <activity 
            android:name=".ShowActivityLight" 
            android:enabled="false"
            android:theme="@style/AppTheme.Light">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
    </application>
</manifest>

然后你可以得到这样的东西来获得主题的Activity类,给出一个抽象的Activity:

public static ComponentName getThemedActivityName(
        Context ctx, 
        Class<? extends Activity> clazz) {

    // Probably gets some value off SharedPreferences
    boolean darkTheme = isUsingDarkTheme(ctx);

    String baseName = clazz.getName();
    String name += (darkTheme) ? "Dark" : "Light";
    return new ComponentName(ctx, name);
}

public static void startThemedActivity(
        Activity ctx, 
        Class<? extends Activity> clazz) {
    Intent intent = new Intent();
    intent.setComponent(getThemedActivityName(ctx, clazz));
    ctx.startActivity(intent);
}

当主题发生变化时,会根据需要更改启用状态。

public void onThemeChanged(Context ctx, boolean dark) {
    // save theme to SharedPreferences or similar and...

    final PackageManager pm = ctx.getPackageManager();
    final String pckgName = ctx.getPackageName();

    final PackageInfo pckgInfo;
    try {
        final int flags = PackageManager.GET_ACTIVITIES 
                             | PackageManager.GET_DISABLED_COMPONENTS;
        pckgInfo = pm.getPackageInfo(pckgName, flags);
    } catch (PackageManager.NameNotFoundException e) {
        throw new RuntimeException(e);
    }

    final ActivityInfo[] activities = pckgInfo.activities;

    for (ActivityInfo info: activities) {
        final boolean enable;
        if (info.theme == R.style.AppTheme_Light) {
            enable = !dark;
        } else if (info.theme == R.style.AppTheme_Dark) {
           enable = dark;
        } else {
           continue;
        }

        final int state = (enable) ? 
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED;

        final String name = info.targetActivity;
        final ComponentName cmp = new ComponentName(pckgName, name);
        pm.setComponentEnabledSetting(cmp, state, PackageManager.DONT_KILL_APP);
    }
}

如果在循环上执行IPC会让您感到害怕,只要对onThemeChanged()的多次调用按顺序运行,就可以在辅助线程上异步执行此操作。

请注意,在此示例中,我更改了所有活动(具有已知主题)的已启用状态,但只需对具有intent过滤器的活动执行此操作。如果活动没有硬编码,这样就更容易了。

重要说明:正如Richard Le Mesurier和其他人所指出的那样,在启动器活动中使用此技术会删除或禁用主屏幕上的快捷方式(如果存在)。这只是非启动器活动的解决方案。