Темная тема в тренде. В приложениях под Android набирает популярность возможность переключения оформлений. Рассказываю как реализовать такой переключатель.
Есть как минимум два варианта реализации смены тем
- С пересозданием Activity/Fragment
- Без пересоздания Activity/Fragment
Смена темы с пересозданием Activity
Суть подхода в том, что при создании Activity все ваши элемента интерфейса отрисовываются согласно заданной темы приложения. Но ведь приложению можно программно подсунуть другой стиль. Останется только пересоздать Activity, а все цвета автоматически перекрасятся в новую палитру.
Тут все просто, вы вызываете setTheme() и после этого recreate().
Важный момент, если вы хотите установить тему непосредственно в onCreate()
, то делайте это перед вызовом super
@Override protected void onCreate(Bundle savedInstanceState) { setTheme(myAwesomeTheme); super.onCreate(savedInstanceState); ...
Плюсы подхода
- Минимальное вмешательство в код приложения
- Гарантия консистентности внешнего вида
Минусы подхода
- Пересоздание сложного UI может вызвать заметные лаги
- Если есть элемент, который не восстанавливает свое состояние — будет выглядеть как баг
Смена темы без пересоздания Activity
Если вы хотите избавится от пересоздания UI, то придется поработать. Этот подход меняет общую тему приложения и в то же время уведомляет уже готовый интерфейс об изменениях в Runtime. Каждый элемент вашего экрана реагирует на изменение и перерисовывает себя.
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_toggle: if (mIsLightTheme) { mIsLightTheme = false; setTheme(R.style.AppThemeDark); setSystemBarTheme(true); } else { mIsLightTheme = true; setTheme(R.style.AppThemeLight); setSystemBarTheme(false); } break; } // уведомление о смене темы // Меняем "системный UI" прямо на месте TypedValue typedValue = new TypedValue(); Resources.Theme theme = getTheme(); theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true); getWindow().getDecorView().setBackgroundColor(typedValue.data); theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true); getSupportActionBar().setBackgroundDrawable(new ColorDrawable(typedValue.data)); theme.resolveAttribute(android.R.attr.colorPrimaryDark, typedValue, true); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(typedValue.data); return super.onOptionsItemSelected(item); }
В GIF примере scrollview таким же образом переопределяет свой фон
... TypedValue typedValue = new TypedValue(); Resources.Theme theme = getContext().getTheme(); theme.resolveAttribute(R.attr.bgChartColor, typedValue, true); setBackgroundColor(typedValue.data); ...
А графики рисуются через onDraw()
, Paint
для который аналогично меняет цвет при общей смене стилей
theme.resolveAttribute(R.attr.axisChartColor, typedValue, true); mAxisPaint = new Paint(); mAxisPaint.setColor(typedValue.data);
Плюсы подхода
- Не тратится время на пересоздание элементов
- Состояние UI будет консистентно (даже fling в scrollview на месте)
Минусы подхода
- Нужно следить за каждым элементом и обрабатывать смену темы для него отдельно
Я верю, что смена темы это редкая операция. Принимая это во внимание, в большинстве случаев резонно пожертвовать минусами пересоздания Acitivity в пользу легкости реализации. И потратить сэкономленное время на устранение возможных проблем с производительностью или реализацию красивых переходов между экранами. Так и performance будет на высоте и общий look & feel приложения выиграет.