Как создавать нестандартные фигуры с помощью MaterialShapeDrawable +12


image

В Material Design 2.0 появилось несколько новых концепций. Одна из них — особое внимание к геометрическим формам элементов интерфейса. И есть простой способ делать красивые нестандартные формы. Он называется MaterialShapeDrawable. Давайте разберемся, насколько он полезен и прост в использовании.


Добавить элементы графики на экран Android-приложения можно несколькими способами. Самый незамысловатый — импортировать растровые изображения в форматах webp или png. Другая опция — использовать VectorDrawable, который позволяет растянуть картинку до размера, который нужен. Еще один способ добавить графику на экран — использовать ShapeDrawable. Последние являются самым несложным способом добавить простой фон или даже создать иконку. Изображения, созданные с помощью ShapeDrawable, не зависят от плотности пикселей на экране. Они могут быть описаны в xml файле, являться частью другого графического ресурса (например, StateListDrawable) и присутствуют в Android SDK, начиная с API v.1.


Чтобы поменять внешний вид фигуры, мы можем изменить несколько свойств ShapeDrawable: название фигуры, цвет заливки (или градиент), цвет границы фигуры. Для прямоугольников так же можно задать радиус закругления углов. С помощью этих свойств можно создать графику, которая будет использоваться в качестве отдельных изображений на экране, разделителей, фона кнопок или для любых других целей. Если приложение использует API v.21+ и ShapeDrawable в качестве фона для элементов с elevation, то тень под этими элементами также будет правильной формы:


image


ShapeDrawable — удобный инструмент, который почти всегда хорошо решал свои задачи, но с появлением Material Design 2.0 разработчикам нужно что-то более гибкое. Система нового дизайна поощряет использование разных форм, чтобы подчеркнуть их смысл, состояние и индивидуальный стиль приложения. Я выше написал, что есть несколько способов сделать желаемое, но самый простой из них больше не актуален. Значит ли это, что нам нужно начать использовать векторную графику и тем самым терять возможность «бесплатного» создания теней для элементов интерфейса с elevation? Или стоит вообще откатиться к использованию растровых изображений и создавать графические ресурсы для шести возможных плотностей пикселей на экране? К счастью, с появлением Material Design 2.0 появилась абсолютно новая библиотека компонентов.


Эта библиотека создана, чтобы унифицировать внешний вид и поведение UI компонентов Material Design на всех версиях Android и других платформах (есть версии этой библиотеки для iOS, веба и для Flutter). В библиотеке компонентов реализованы многие фичи для нового Material Design. Например, в нее включен компонент BottomAppBar с ожидаемым поведением. Среди прочих компонентов и утилит есть класс MaterialShapeDrawable. На мой взгляд, это необходимый инструмент для решения задач, которые ставит перед разработчиками новая дизайн-система.


Хотя MaterialShapeDrawable все еще считается экспериментальным в релизе библиотеки 1.0.0, его вполне можно использовать для создания классных эффектов в приложении. В классе MaterialShapeDrawable можно описать фигуру указанием вида ее сторон и каждого ее угла. Эти заданные свойства могут управляться интерполятором, что позволяет их анимировать.


Чтобы создать собственный MaterialShapeDrawable, можно использовать конструктор, которому нужно передать объект типа ShapePathModel в параметрах. Он хранит в себе информацию о каждой стороне и каждом угле фигуры в классах EdgeTreatment и CornerTreatment соответственно (сторон и углов всегда ровно четыре, но это не мешает описать практически любую фигуру с их помощью). Можно задать описания персонально для каждой стороны и угла, либо задать их один раз сразу для всей фигуры вызовом одного метода.


В библиотеке уже есть несколько готовых к использованию описаний (treatment) сторон и углов, которые включают большую часть нововведений касательно формы компонентов, представленных в Material Design 2.0. Уже присутствуют: RoundedCornerTreatment — для округленных углов, CutCornerTreatment — для срезанных углов, TriangleEdgeTreatment — для выреза или добавления треугольника к стороне. Для демонстрации их работы есть простой пример:


val shapePathModel = ShapePathModel().apply {
    setAllCorners(CutCornerTreatment(dip(5).toFloat()))
    setAllEdges(TriangleEdgeTreatment(dip(5).toFloat(), true))
}

val backgroundDrawable = MaterialShapeDrawable(shapePathModel).apply {
    setTint(ContextCompat.getColor(this@MainActivity, R.color.colorPrimary))
    paintStyle = Paint.Style.FILL
}

textView.background = backgroundDrawable

Это будет выглядеть так:


image


Само собой, можно просто создавать собственные описания граней и углов. Форма всегда задается для верхнего левого элемента, а Drawable сам сделает операции поворота/отражения, чтобы получить законченную фигуру. Для демонстрации этого есть небольшой пример:


class CutoutCornersTreatment(val size: Float) : CornerTreatment() {

    override fun getCornerPath(angle: Float, interpolation: Float, shapePath: ShapePath) {
        shapePath.reset(0.0f, size * interpolation)
        shapePath.lineTo(size * interpolation, size * interpolation)
        shapePath.lineTo(size * interpolation, 0f)
    }
}

class CurvedEdgeTreatment(val size: Float) : EdgeTreatment() {

    override fun getEdgePath(length: Float, interpolation: Float, shapePath: ShapePath) {
        shapePath.quadToPoint(length / 2f, size * interpolation, length, 0f)
    }
}

Если этот MaterialShapeDrawable использовать в качестве фона, то результат будет таким:


image


В пакете bottomappbar новой библиотеки Material-компонентов есть BottomAppBarTopEdgeTreatment. Он описывает «вырез» в BottomAppBar для кнопки FloatingActionButton. Его верхнюю сторону можно анимировать в зависимости от положения и размеров кнопки. Советую почитать код этих классов, чтобы своими глазами увидеть, что MaterialShapeDrawable — очень гибкий в использовании, и им можно сделать практически все.


Если говорить об обычном ShapeDrawable, есть ещё одна деталь, о которой стоит упомянуть — способность отбрасывать тень формы, соответствующей контуру. Так как теперь можно создавать контуры очень необычных форм с помощью MaterialShapeDrawable, было бы разочарованием не приводить форму тени к форме изображения, особенно когда эти тени можно увидеть везде в Matarial Design 2.0. MaterialShapeDrawable также рассчитывает внешний вид тени. Используя свойство shadowEnabled, можно включить тень, которая в точности будет повторять контур самой фигуры, так же возможно определить радиус, высоту (elevation) и цвет тени. Звучит слишком хорошо, чтоб быть правдой? К сожалению, да. Если использовать тень у MaterialShapeDrawable, то получится нормальная тень (нарисованная методом setShadowLayer() класса Paint, который создан для рисования теней у текста), но обрезанная по границам UI-компонента, в котором отображается полученный результат:


image


Нужно помнить, что MaterialShapeDrawable все еще считается экспериментальным, как и его API, и может меняться в будущем. Также стоит отметить, что код новой библиотеки Material Components открыт, поэтому создание тикетов в баг-трекере или даже пулл-реквестов с исправлением известных проблем очень приветствуется. На самом деле, когда вы читаете эти строки, API уже немного отличается в master-ветке библиотеки (например, вместо ShapePathModel будет использоваться ShapeAppearanceModel), что говорит об активной работе. Одной из многообещающих фич в следующем релизе может быть возможность определить вид сторон/углов по-умолчанию для всей темы приложения. Об этом можно узнать подробнее из официальной документации или исходников библиотеки.


От переводчика:
Классно, что появляются инструменты, которые добавляют новые возможности к обычным ShapeDrawable и помогают создавать не самую простую графику. Это позволяет разработчику не дергать дизайнера по каждой мелочи, а решать вопрос самому, и требует заметно меньше времени. Ждём следующих версий библиотеки Material Components, чтобы попробовать на практике новые способы решить существующие проблемы.




К сожалению, не доступен сервер mySQL