Темная тема в тренде. В приложениях под 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 приложения выиграет.
