August 27, 2019

Темы и стили в Android без магии. И как их готовить с SwitchCompat

В предыдущей статье мы рассмотрели, как использовать темы и стили на уровне кода на примере кастомной view. В этой статье давайте разберем несколько способов стилизации стандартного ui элемента, а в частности SwitchCompat.

Введение

Не всегда оформление по умолчанию стандартного UI элемента устраивает дизайнера. Давайте разберем, как поменять внешний вид элемента на примере SwitchCompat.

Для решения задачи нам нужно:

  • Создать свой стиль для SwitchCompat.
  • Каким-то образом задать этот стиль SwitchCompat.

Назначить стиль SwitchCompat можно несколькими способами, например:

  • Указывать для каждой view в верстке экранов через атрибут style.
  • Создать тему с переопределенным атрибутом switchStyle и назначить эту тему в манифесте для всего приложения или конкретной активити. Это изменит внешний вид view для всего приложения/активити.
  • Тему также можно установить программно, в коде активити. При необходимости ее можно менять «на лету».

Новый стиль для SwitchCompat

В ресурсах создадим новый стиль MySwitchStyle, наследуем оформление от Widget.AppCompat.CompoundButton.Switch, задав parent. Можно и не наследовать, но тогда придется указать все значения, даже которые мы не планируем менять.

<style name="MySwitchStyle" parent = "Widget.AppCompat.CompoundButton.Switch"> </style>

Чтобы что-то изменить, надо переопределить требуемые атрибуты. Атрибуты можно посмотреть в документации.

В документации видим несколько атрибутов. Они указаны в виде, как если бы мы обращались к ним в коде (например, вот так R.styleable.SwitchCompat_android_thumb). Я расшифрую только часть из них, чтобы не было сомнений. Назначение остальных несложно понять из документации.

  • android:thumb — ресурс для подвижной части SwitchCompat,
  • track — ресурс для неподвижной части SwitchCompat,
  • thumbTint — позволяет окрашивать подвижную часть в нужные цвета в зависимости от состояния SwitchCompat,
  • trackTint — позволяет окрашивать неподвижную часть в нужные цвета в зависимости от состояния SwitchCompat.

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

Нам понадобится селектор в папке color наших ресурсов. Файл selector_switch_thumb.xml:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="@android:color/holo_orange_dark"/>
    <item android:color="@android:color/holo_green_light"/>
</selector>

Теперь зададим атрибут thumbTint в нашем стиле.

<style name="MySwitchStyle" parent="Widget.AppCompat.CompoundButton.Switch">
    <item name="thumbTint">@color/selector_switch_thumb</item>
</style>

Теперь все SwitchCompat, получившие каким-то образом стиль MySwitchStyle, будут выглядеть по-новому.

Стиль в верстке

Самый тривиальный и негибкий способ.

  • Стиль применяется при inflate ресурса layout.
  • Повлиять программно мы никак не можем.
  • Указывать каждый раз в верстке неудобно. И можем забыть.
<androidx.appcompat.widget.SwitchCompat
    android:text="Themed switch"
    style="@style/MySwitchStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Стиль в теме. Тема назначается через Manifest

Создаем тему AppTheme и задаем значение атрибуту switchStyle. Значением является наш стиль MySwitchStyle.

<resources>
    <style name="CustomTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <!-- Задаем значение атрибуту switchStyle -->
        <item name="switchStyle">@style/MySwitchStyle</item>
    </style>
</resources>

Тема может быть указана в манифесте для всего приложения

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/CustomTheme"> <!-- Тема для всего приложения -->

</application>

Или для конкретной активити

<activity
    android:name=".MainActivity"
    android:theme="@style/CustomTheme"> <!-- Тема для активити -->
</activity>

Теперь все SwitchCompat будут иметь новый внешний вид. Без изменения в верстке.

  • Плюсы — Можем менять внешний вид для всего приложения сразу.
  • Минусы — налету менять не получится.

Стиль в теме. Тема назначается программно

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

Давайте менять тему активити в зависимости от состояния Switch.

private const val KEY_CUSTOM_THEME_CHECKED = "KEY_CUSTOM_THEME_CHECKED"

class MainActivity : AppCompatActivity() {
    private val preference by lazy {
        PreferenceManager.getDefaultSharedPreferences(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        val isCustomThemeChecked = preference.getBoolean(KEY_CUSTOM_THEME_CHECKED, true)
        if (isCustomThemeChecked) {
            setTheme(R.style.CustomTheme)
        } else {
            setTheme(R.style.StandardTheme)
        }
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        customThemeCheckbox.isChecked = isCustomThemeChecked
        customThemeCheckbox.setOnCheckedChangeListener { _, isChecked ->
            preference.edit()
                .putBoolean(KEY_CUSTOM_THEME_CHECKED, isChecked)
                .apply()
            recreate()
        }
    }
}

1. Устанавливаем тему программно, вызвав setTheme. Метод надо вызывать до super.onCreate(savedInstanceState). В onCreate у нас происходит инициализация фрагментов (когда они есть).

  1. Задаем начальное состояние Switch в зависимости от темы.
  2. Устанавливаем листенер, который при изменении Switch меняет тему в настройках и перезапускает активити через метод активити recreate().

Результат:

Остальной код

<resources>
    <style name="CustomTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="switchStyle">@style/MySwitchStyle</item>
    </style>

    <style name="StandardTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="MySwitchStyle" parent="Widget.AppCompat.CompoundButton.Switch">
        <item name="thumbTint">@color/selector_switch_thumb</item>
    </style>
</resources>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:orientation="vertical">

    <CheckBox
        android:text="CustomTheme"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:saveEnabled="false"
        android:id="@+id/customThemeCheckbox"/>

    <androidx.appcompat.widget.SwitchCompat
        android:text="Themed switch"
        android:layout_marginTop="56dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/themedSwitch"/>

</LinearLayout>

Другие View

Чтобы переопределить стиль для SwitсhView для всего приложения, мы переопределили значение атрибута switchStyle. Можно догадаться, что такие атрибуты есть и для других View.

Например:

  • editTextStyle
  • checkboxStyle
  • radioButtonStyle

Как их искать? Я просто смотрю исходники через Android Studio.

Заходим в тему, зажимаем ctrl, кликаем на родителе нашей темы. Смотрим, как описывают тему ребята из Google. Смотрим, какой атрибут определяется и от какого стиля можно отнаследоваться. Пользуемся.

Кусок из темы Base.V7.Theme.AppCompat.Light.

<item name="editTextStyle">@style/Widget.AppCompat.EditText</item>
<item name="checkboxStyle">@style/Widget.AppCompat.CompoundButton.CheckBox</item>
<item name="radioButtonStyle"> @style/Widget.AppCompat.CompoundButton.RadioButton</item>
<item name="buttonStyle">@style/Widget.AppCompat.Button</item>

Ресурсы

developer.android.com/guide/topics/ui/look-and-feel/themes

developer.android.com/reference/android/support/v7/widget/SwitchCompat.html#xml-attributes

P.S.

Статья не претендует на полный справочник. Код умышленно сокращен. Я ставил задачу дать общее понимание — как это работает и зачем это нужно. Дальше все легко ищется в документации и в стандартных ресурсах.

Источник: Темы и стили в Android без магии. И как их готовить с SwitchCompat