Как установить тему для приложения, чтобы избежать неправильных переходов цвета?

Фон

Я разрабатываю функцию выбора тем в моем приложении app manager, и мне это удалось динамически настраивая тему для каждого из действий.

СНОВА: речь идет не о настройке темы для действий. Это действительно отлично работает для меня.

Проблема

Признаки показывают правильную тему, но само приложение при запуске приложения показывает неправильное, независимо от того, что я делаю.

Это проблема, потому что, когда пользователь открывает приложение, он увидит фон темы приложения, и только через несколько секунд активность будет показана с темой, которую выбрал пользователь.

Итак, если приложение имеет белый фон, а пользователь выбрал тему с черным фоном, порядок будет следующим:

Приложение показывает белый фон → активность начинается и отображается черный фон.

В скриншотах:

enter image description here

Так что это неправильно. В этом случае мне нужно, чтобы он отображал черный-черный фон.

Только если пользователь выбрал тему на основе голосовой подсветки (по умолчанию это приложение), она отлично работает, так как цвет соответствует значению активности, показанного справа при открытии приложения.

Что я пробовал

У меня возникла идея настроить тему приложения на пустое место, надеясь, что никакой переход не будет показан, используя что-то вроде:

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

Фактически, некоторые люди предложили аналогичное решение.

Это работает, но это приводит к плохому опыту, поскольку на некоторых устройствах требуется некоторое время, пока не будет показано первое действие, и, как таковое, существует значительное количество времени, которое пользователь вообще ничего не видит, как если бы приложение не запускается.

Вопрос

Как это решить?

Я уже пробовал задавать тему в классе, который простирается от приложения, но он ничего не делает, независимо от того, где в этом классе я его называю.

Ответы

Ответ 1

Прозрачная тема приложения с затухающей анимацией

Мое первоначальное предложение состояло в том, чтобы использовать прозрачную полноэкранную тему приложения (без панели действий).

В сочетании с этим я всегда предлагаю альфа-анимацию перейти от темы приложения к теме активности. Это предотвращает сотрясение пользователя при появлении панели действий.

Код OP останется почти идентичным, за исключением изменения темы манифеста и добавления альфа-анимации в ваш метод onCreate() для некоторого базового класса активности, как в примерах ниже:


тема манифеста определяется как:

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, что это было рассмотрено. Он также включен в ответ dentex. Однако, как утверждает OP, слабость, особенно на старых устройствах, заключается в том, что пользователь не получает отзывов при попытке запустить приложение. Похоже, приложение слишком долго запускается.

В KitKat это не так, поскольку строка состояния и программные клавиши меняются от прозрачного до черного, а остальная часть экрана по-прежнему прозрачна.

Другой подход к этому подходу - использовать полноэкранный черный фон в качестве темы приложения. Это то, что было сделано Bitspin для Timely, которые были приобретены Google, по-видимому, на основе потрясающего пользовательского интерфейса в этом приложении. По-видимому, этот метод вполне допустим во многих случаях.


Обновление # 2:

Чтобы ускорить восприятие запуска, альтернативой обычной черной теме является использование полноэкранного изображения с логотипом приложения в центре - стиль "заставки". Снова перейдем к активности после запуска.

Это невозможно для прозрачной темы, используя прозрачный полноэкранный образ. Android игнорирует прозрачность изображения (или накладывает прозрачное изображение на черный фон). Это было отмечено ОП в комментариях.

Мы можем либо иметь прозрачную тему без изображения, либо непрозрачную тему с изображением (возможно, интересная тема для другого вопроса).


Заметка об использовании псевдонимов манифеста

Еще одно предложение @sergio91pt - использовать псевдонимы для различных действий в манифесте.

Хотя в некоторых случаях это может быть полезным методом, в этом случае он имеет некоторые недостатки:

  • Любой ярлык на экране, который пользователь создал для этой операции, перестанет работать, когда изменяется основной псевдоним пусковой установки, каждый раз, когда пользователь меняет темы.
  • Некоторые устройства/пусковые установки довольно медленно активируют и дезактивируют разные псевдонимы. По моему опыту это может занять несколько секунд (Galaxy Nexus 4.1 iirc), в течение которого у вас либо нет видимого значка запуска, либо у вас есть 2 значка.
  • Каждая возможная тема требует другого псевдонима - это может оказаться громоздким, если существует много разных тем.

Ответ 2

Немного поздно, но это может быть ответ. Я обнаружил это случайно.

Нет активности входа, нет настраиваемых анимаций, нет взлома. Просто атрибут в теме. Android похоронил это глубоко внутри своих ресурсов.

Добавьте к вашему приложению тему:

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

И все готово. Наслаждайтесь этим!

Ответ 3

Чтобы зафиксировать любое мерцание (панель действий, название...) при запуске приложения, я установил в манифест

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

для моих основных видов деятельности (контейнер табуляции и активность настроек, откуда я переключаю темы, основываясь на голосе темного и светлого)

Если вы используете некоторую "активность запуска" или "активность всплеска", примените также Theme.NoTitleBar для них:

объявив Theme.NoTitleBar, для каждого действия, в onCreate вам нужно:

  • правильно установите заголовок с помощью setTitle(...) и THEN

  • задайте тему с помощью setTheme(R.style.CustomAppTheme) ПЕРЕД setContentView(...)
    (и вы уже это делаете);

Это предотвратит мигание панели действий/заголовка при переключении темы (если выполняется "на лету" ) и при запуске приложения.

Если вам нужен внешний вид пользовательской панели действий, это означает, что стандартная панель действий голоса не будет мигать перед вашим.

Ответ 4

Цвет перехода извлекается из темы активности манифеста (или приложения, если оно не установлено).

В настоящее время единственным способом, связанным с этим ограничением, является создание фиктивного подкласса для каждой реальной операции, например. MyActivityLight, чтобы объявить другую тему. Алиас активности не будет работать, атрибут будет проигнорирован.

Для действий с IntentFilter вы должны поддерживать только один из каждого типа, используя PackageManager#setComponentEnabledSetting(). Обратите внимание, что изменение может занять несколько секунд.

Для действий, запущенных по имени класса, вы можете сделать правильный префикс в соответствии с пользовательской темой.


Итак, давайте предположим, что у вас есть две темы: AppTheme.Dark и AppTheme.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>

Измените все действия выше как абстрактные классы и создайте фиктивные подклассы, помеченные Light и Dark.

Затем манифест должен быть изменен следующим образом:

<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, с учетом абстрактного действия:

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() выполняются последовательно.

Обратите внимание, что в этом примере я изменяю активированный статус всех действий (имеющих известную тему), но должен был сделать это только для тех, у кого есть фильтры намерения. Если действия не жестко запрограммированы, тем проще.

Важное примечание: Как Richard Le Mesurier и другие указали, что использование этой техники в Launcher Activities устраняет или отключает ярлык на главный экран, если он существует. Это просто решение для действий без запуска.