[Из песочницы] Темы, стили и другие

k3uv8qyf2au0mtakqlf_y5lt7no.png

Практически все разработчики знают, что в андроиде есть Темы, но применение их обычно ограничивается копированием кусков xml из Stack Overflow или других ресурсов. В интернете есть информация по темам, но это обычно просто рецепт, как добиться определенного результата. В этой статье я постарался дать вводный обзор механизма стилизации андроида.

Содержание


Введение
Атрибуты для RectView
Cтиль для RectView
Стиль по умолчанию
Атрибут темы для стиля RectView
Тема на уровне Активити
Тема на уровне вью
Порядок применения значений атрибутов
Темы в ресурсах
Итоги

Введение


Стили и Темы в андроиде — это механизмы, позволяющее отделить детали оформления (например, цвет, размер шрифта и т.д) от структуры UI. Разобраться, как это работает, нам поможет простой пример с кастомной вью.

Нам понадобится простенькая кастомная вью, которая будет рисовать прямоугольник с нужным цветом в границах вью.

class RectView @JvmOverloads constructor(
    context: Context,
    attrSet: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrSet, defStyleAttr) {

    private val paint = Paint().apply {
        color = Color.BLUE
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
    }
}


foz6kcwbxxgp5qp94cr5llgko5i.png

Атрибуты для RectView


Теперь хотелось бы иметь возможность менять цвет прямоугольника из верстки.
Для этого нам надо добавить новый атрибут, добавим в файл attrs.xml


    


Теперь мы можем в коде обращаться к id этого атрибута через R.attr.rectColor и
в верстке экрана мы можем использовать атрибут app: rectColor.


Но RectView еще не знает, что существует атрибут, где можно взять цвет для прямоугольника.

Давайте научим RectView понимать атрибут rectColor,
добавим группу атрибутов


    

    
        
    


В классе R сгенерировались 2 новых поля:
R.styleable.RectView — это массив id атрибутов, в данный момент это массив из одного элемента R.attr.rectColor
R.styleable.RectView_rectColor — это индекс id атрибута в массиве R.styleable.RectView, т.е. id атрибута мы можем получить и так R.styleable.RectView[R.styleable.RectView_rectColor]

И добавим в код поддержку атрибута rectColor.

init {
        val typedArray = context.theme.obtainStyledAttributes(
            attrSet,
            R.styleable.RectView,
            0,
            0
        )
        try {
            paint.color = typedArray.getColor(
                R.styleable.RectView_rectColor,
                Color.BLUE
            )
        } finally {
            typedArray.recycle()
        }
}
Полный код
class RectView @JvmOverloads constructor(
    context: Context,
    attrSet: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrSet, defStyleAttr) {

    private val paint = Paint().apply {
        color = Color.BLUE
    }

    init {
        val typedArray = context.theme.obtainStyledAttributes(
            attrSet,
            R.styleable.RectView,
            0,
            0
        )
        try {
            paint.color = typedArray.getColor(
                R.styleable.RectView_rectColor,
                Color.BLUE
            )
        } finally {
            typedArray.recycle()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
    }
}


Теперь мы можем менять цвет прямоугольника из верстки
uy-l7amanlzduejh_cxoawzv3qg.png

Cтиль для RectView


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

Решение вынести все в стиль.

Теперь мы можем создать сколько угодно вью с одинаковым оформлением, указав style.

Стиль по умолчанию


Теперь мы хотим, чтобы все вью по умолчанию имели какой-то стиль. Для этого мы укажем стиль по умолчанию для метода obtainStyledAttributes

context.theme.obtainStyledAttributes(
            attrSet,
            R.styleable.RectView,
            0,
            R.style.DefaultRectViewStyle
)

Этого достаточно. Теперь все RectView, которые мы добавляем в верстку, будут иметь стиль по умолчанию DefaultRectViewStyle.

Атрибут темы для стиля RectView


Значение по умолчанию — это хорошо, но хотелось бы управляет оформлением гибче. Удобно задавать стиль конкретного вью на все приложение целиком или для отдельного Activity.

Для этого нам понадобится новый атрибут. Его значение мы будем задавать в теме, значением будет стиль, определяющий внешний вид RectView.

И научим наш rectView понимать этот атрибут темы. Передав в метод obtainStyledAttributes третий параметр R.attr.rectViewStyle.


context.theme.obtainStyledAttributes(
                attrSet,
                R.styleable.RectView,
                R.attr.rectViewStyle,
                R.style.DefaultRectViewStyle
            )


Теперь, если в теме будет задан item с именем rectViewStyle и значением типа стиль, то этот стиль применится ко всем RectView с этой темой.

Тема на уровне Активити


Зададим значение атрибуту rectViewStyle в нашей теме.


    

Указываем тему в манифесте приложения.


    
        
            
                
                
            
        
    


Всем активити будет установлена тема AppTheme по умолчанию.
Мы задали оформление RectView для всего приложения.

Тему также можно задать для конкретной Activity в манифесте


Тут важно, что тема, которую мы будем задавать на уровне приложения или активити, должна быть унаследована от стандартной темы. Например, Theme.AppCompat. Иначе мы получим краш в рантайме.

java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

Тема на уровне вью


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

Здесь мы задали тему MyOverlay группе LinearLayout и всем его потомкам по иерархии.


    
 

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

Порядок применения значений атрибутов


9wjmqvlao88ces3wkmxufjfvgj8.png
Если для вью мы определяем значения атрибута и в верстке и в стиле и в теме, то порядок выбора значения будет следующим. Самый высокий приоритет у значения, которое задано в верстке, т.е. если значение задано в верстке, то стиль и тема будут игнорироваться, дальше идет стиль, потом тема и на последнем месте стиль по умолчанию.

Темы в ресурсах


Значения темы можно использовать в ресурсах. Например, в drawable мы можем задать цвет, который будет зависеть от цвета, установленного в теме.



    

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

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

Итоги


  • Тема и стиль на уровне ресурсов — одно и то же, но используются по-разному.
  • Тема может содержать в себе другие темы, просто значения или стили для вьюх.
  • Стиль указывается в верстке на уровне вью. LayoutInflater считает значения стиля и передаст их как AttributeSet в конструктор View.
  • Темы — это механизм, позволяющий определить оформление глобально для всей активити.
  • Значения темы можно менять для элемента (и потомков) в иерархии вью.
  • Значения темы можно использовать не только во View, но и в ресурсах.

PS: Если статья будет интересна, напишу более продвинутую статью или статью с большим количеством реальных примеров.

© Habrahabr.ru