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


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

    Содержание


    Введение
    Новый стиль для switchStyle
    Стиль в верстке
    Стиль в теме. Тема назначается через Manifest.
    Стиль в теме. Тема назначается программно.
    Другие View

    Введение


    Не всегда оформление по умолчанию стандартного 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). Я расшифрую только часть из них, чтобы не было сомнений. Назначение остальных несложно понять из документации.
    В коде
    В xml
    SwitchCompat_android_thumb
    android:thumb
    SwitchCompat_thumbTint
    thumbTint
    SwitchCompat_track
    track
    SwitchCompat_trackTint
    trackTint

    • 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 у нас происходит инициализация фрагментов (когда они есть).
    2. Задаем начальное состояние Switch в зависимости от темы.
    3. Устанавливаем листенер, который при изменении 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.

    Статья не претендует на полный справочник. Код умышленно сокращен. Я ставил задачу дать общее понимание — как это работает и зачем это нужно. Дальше все легко ищется в документации и в стандартных ресурсах.
    • +14
    • 4,3k
    • 1
    Поддержать автора
    Поделиться публикацией

    Комментарии 1

      +1
      Спасибо! Если есть чем продолжить — жду еще)

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое