Как добавить тень в android studio
Перейти к содержимому

Как добавить тень в android studio

  • автор:

TextView

Компонент TextView предназначен для отображения текста без возможности редактирования его пользователем, что видно из его названия (Text — текст, view — просмотр).

Находится в разделе Texts.

TextView — один из самых используемых компонентов. С его помощью пользователю удобнее ориентироваться в программе. По сути, это как таблички: Руками не трогать, По газону не ходить, Вход с собаками воспрещен, Часы работы с 9.00 до 18.00 и т.д., и служит для представления пользователю описательного текста.

Для отображения текста в TextView в файле разметки используется атрибут android:text, например:

 android:text="Погладь кота, . " 

Такой подход является нежелательным. Рекомендуется всегда использовать текстовые ресурсы. В будущем эта привычка позволит вам обеспечить многоязыковую поддержку:

 android:text="@string/hello" 

Программная установка текста

Программно текст можно задать методом setText():

 // Инициализируем компонент TextView textView = findViewById(R.id.textView); // задаём текст textView.setText("Hello Kitty!"); // или с использованием текстовых ресурсов textView.setText(R.string.hello); 

Атрибуты

android:textsize размер текста. При установке размера текста используется несколько единиц измерения: px (пиксели), dp, sp, in (дюймы), pt, mm. Для текстов рекомендуется использовать sp: android:textSize=»48sp», аналог — метод setTextSize() android:textstyle стиль текста. Используются константы: normal, bold, italic. Например, android:textStyle=»bold» выводит текст жирным android:textcolor цвет текста. Используются четыре формата в шестнадцатеричной кодировке: #RGB; #ARGB; #RRGGBB; #AARRGGBB, где R, G, B — соответствующий цвет, А — прозрачность (alpha-канал). Значение А, установленное в 0, означает прозрачность 100%.

Для всех вышеперечисленных атрибутов в классе TextView есть соответствующие методы для чтения или задания соответствующих свойств.

Программно установим размеры текста при помощи setTextSize() с различными единицами измерения.

 // 20 DIP (Device Independent Pixels) textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); // 0.5 inch textView.setTextSize(TypedValue.COMPLEX_UNIT_IN, 0.5f); // 10 millimeter textView.setTextSize(TypedValue.COMPLEX_UNIT_MM, 10); // 30 points textView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 30); // 30 raw pixels textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 30); // 30 scaled pixels textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30); 

TextView

По умолчанию у компонентов TextView отсутствует фоновый цвет. Чтобы задать цвет, укажите значение Drawable для атрибута android:background. В качестве значения Drawable может использоваться изображение или XML-представление фигуры, включающий ресурс Drawable (поместить в папку res/drawable).

Программная установка фона

В некоторых случаях программисты из-за невнимательности неправильно меняют фон элемента программным способом и удивляются, почему ничего не работает.

Предположим, у вас определён в ресурсах зелёный цвет:

 #337700 

Следующий код будет ошибочным:

 textview.setBackgroundColor(R.color.tvBackground); // не работает 

Нужно так (два варианта):

 textView.setBackgroundResource(R.color.tvBackground); // первый вариант textView.setBackgroundColor(getResources().getColor(R.color.tvBackground)); // второй вариант 

Реагируем на событие onClick

Если вы хотите, чтобы TextView обрабатывал нажатия (атрибут android:onClick), то не забывайте также использовать в связке атрибут android:clickable=»true». Иначе работать не будет!

Многострочный текст

Если вы хотите создать многострочный текст в TextView, то используйте символы \n для переноса строк.

Например, в ресурсах:

  У лукоморья дуб зелёный;\n Златая цепь на дубе том:\n И днём и ночью кот учёный\n Всё ходит по цепи кругом;\n Идёт направо - песнь заводит,\n Налево - сказку говорит. 

Обратите внимание, что в тексте также применяется простое форматирование.

Также перенос на новую строку можно задать в коде:

 textView.setText("Первая строка \nВторая строка \nТретья строка"); 

Увеличиваем интервалы между строками

Вы можете управлять интервалом между соседними строчками текста через атрибут android:lineSpacingMultiplier, который является множителем. Установите дробное значение меньше единицы, чтобы сократить интервал или больше единицы, чтобы увеличить интервал между строками.

 android:lineSpacingMultiplier="0.8" 

Бой с тенью

Чтобы оживить текст, можно дополнительно задействовать атрибуты для создания эффектов тени: shadowColor, shadowDx, shadowDy и shadowRadius. С их помощью вы можете установить цвет тени и ее смещение. Во время установки значений вы не увидите изменений, необходимо запустить пример в эмуляторе или на устройстве. В следующем примере я создал тень красного цвета со смещением в 2 пикселя по вертикали и горизонтали. Учтите, что для смещения используются единицы px (пиксели), единицы dp не поддерживаются.

TextView с тенью

Программный эквивалент — метод public void setShadowLayer (float radius, float dx, float dy, int color):

 TextView textShadow = (TextView)findViewById(R.id.hello); textShadow.setShadowLayer( 5f, //float radius 10f, //float dx 10f, //float dy 0xFFFFFFFF //int color ); 

Создание ссылок автоматом

У TextView есть ещё два интересных свойства Auto link (атрибут autoLink) и Links clickable (атрибут linksClickable), которые позволяют автоматически создавать ссылки из текста.

Выглядит это следующим образом. Предположим, мы присвоим элементу TextView текст Мой сайт: developer.alexanderklimov.ru и применим к нему указанные свойства.

При этом уже на этапе разработки вы увидите, что строка адреса сайта после слов Мой адрес: стала ссылкой. Если вы запустите приложение и нажмете на ссылку, то откроется браузер с указанным адресом. Вам даже не придется писать дополнительный код. Аналогично, если указать номер телефона (параметр phone), то запустится звонилка.

У ссылки есть интересная особенность — при длительном нажатии на ссылку появляется диалоговое окно, позволяющее скопировать ссылку в буфер обмена.

Атрибут autoLink позволяет комбинировать различные виды ссылок для автоматического распознавания: веб-адрес, email, номер телефона.

Ссылка в TextView

Цвет ссылки можно поменять через свойство Text color link (XML-атрибут textColorLink), а программно через метод setTextLinkColor().

Программно можно установить ссылки на текст через класс Linkify:

 TextView tvDisplay = (TextView)findViewById(R.id.tvDisplay); String data = "" + "Пример использования Linkify для создания ссылок в тексте.\n" + "\n" + "URL: http://developer.alexanderklimov.ru/ \n" + "Email: [email protected] \n" + "Телефон: (495)-458-58-29 \n" + "Адрес: 10110 ул.Котовского, г.Мышкин \n" + "\n" + "Классно получилось?"; if(tvDisplay != null)

Ссылка в TextView

Кроме константы ALL, можно также использовать Linkify.EMAIL_ADDRESSES, Linkify.MAP_ADDRESSES, Linkify.PHONE_NUMBERS. К сожалению, русские адреса не распознаются. В моём случае индекс был распознан как телефонный номер, а город и улица не стали ссылкой.

В таких случаях придётся самостоятельно добавить ссылки в текстах. Например, определим ссылку в ресурсе:

Присвоим созданный ресурс тексту в TextView и запустим пример. Сам текст будет выглядеть как ссылка, но реагировать не будет. Чтобы исправить данную проблему, добавим код:

 TextView textView = (TextView) findViewById(R.id.textView); textView.setMovementMethod(LinkMovementMethod.getInstance()); 

Ссылки в тексте выглядят не совсем удобными. Есть отдельная библиотека, которая улучшает функциональность. Описание проблем и ссылка на библиотеку есть в статье A better way to handle links in TextView — Saket Narayan.

Совет: Используйте полупрозрачность с умом

Если вам нужно установить текст полупрозрачным, то не используйте атрибут android:alpha:

Дело в том, что такой подход затрачивает много ресурсов при перерисовке.

Атрибут textColor позволяет установить полупрозрачность без потери производительности:

Выделить текст для копирования

По умолчанию, текст в TextView нельзя выделить для копирования. Но в API 11 появилась такая возможность, которая может пригодиться. Делается либо при помощи XML-атрибута android:textIsSelectable, либо через метод setTextIsSelectable().

Добавьте в разметку два компонента TextView и одно текстовое поле EditText для вставки скопированного текста. У первой текстовой метки установим возможность выделения текста декларативно.

Для второго компонента возможность выделения создадим программно.

 TextView secondTextView = (TextView) findViewById(R.id.textView2); secondTextView.setTextIsSelectable(true); 

Сделайте долгий тап на тексте в любом TextView. Увидите стандартные ползунки для выбора длины текста. Скопируйте текст, сделайте длинный тап в EditText и вставьте текст.

Стили

Выводим разделитель под текстом.

Как добавить тень для текста в Андроид Студии

Урок о том, как в TextView применить тень для текста.

Вам також може сподобатися

Now in Android

Уроки по android разработке на Java 0 1 900
Добро пожаловать в Now in Android, ваше текущее руководство по новинкам и важным событиям

Урок 11. Принципы навигации внутри и между андроид-приложениями

Архитектура андроид-приложений 0 10 231

Продолжая серию уроков о разработке мобильных приложений, мы будем изучать компонент Navigation набора инструментов

Broadcast Receivers

Документация по android 2 31 011

Перевод статьи на Медиуме о технологии Broadcast Receivers (широковещательные приемники). Это компоненты андроид, которые

Урок 4. Android Material Design. Установка теней и обрезка View

Material design вводит понятие высоты для элементов пользовательского интерфейса. Высота помогает пользователям понять относительную важность каждого элемента и сосредоточить свое внимание на задаче.

Высота view представленная с помощью оси Z, определяет внешний вид его тени: view с более высоким значением Z отбрасывают большие, более мягкие тени. View с высшим значением Z перекрывают view с низшим значением Z; однако, значение Z не влияет на размер самого view.

Высота также полезна для создания анимаций, когда виджеты временно поднимаются выше плоскости view при выполнении какого-либо действия.

Для получения дополнительной информации о высоте в материальном дизайне, смотрите Объекты в 3D пространстве.

Установите высоту ваших View

Значение Z для view состоит из двух составляющих:

– Высота (elevation): статическая составляющая.
– Смещение (translation): динамическая составляющая, используемая для анимаций.

Z = elevation + translationZ

shadows-depth

Чтобы установить высоту view в макете используйте атрибут android:elevation. Чтобы установить высоту в коде activity, используйте метод View.setElevation().

Чтобы установить смещение, используйте метод View.setTranslationZ().

Новые методы ViewPropertyAnimator.z() и ViewPropertyAnimator.translationZ() позволяют легко анимировать высоту view. Для получения дополнительной информации, смотрите справку по ViewPropertyAnimator и руководство разработчика о свойствах анимации.

Вы так же можете использовать StateListAnimator чтобы установить анимации декларативно. Это особенно полезно в тех случаях, когда изменяется состояние триггера анимации, например, когда пользователь нажимает на кнопку. Для получения дополнительной информации, смотрите Изменение состояния анимации view.

Значение Z измеряется в dp (density-independent pixels).

Настройте тени и контуры

Границы фона drawable view определяют форму его тени. Контуры представляют собой внешнюю форму графического объекта и определяют область для сенсорного отклика.

Рассмотрим view, определенное с помощью фона dravable:

Фон drawable определен как прямоугольник с закругленными углами:

View отбрасывает тень с закругленными углами, после того как фон drawable установил границы view. Создание пользовательского контура переопределяет форму тени view по умолчанию.

Чтобы установить пользовательский контур для view в коде:

1. Унаследуйтесь от класса ViewOutlineProvider.
2. Переопределите метод getOutline().
3. Назначьте нового поставщика контура для вашего view с помощью метода View.setOutlineProvider() .

Вы можете создать овальные и прямоугольные очертания с закругленными углами используя методы класса Outlines. Поставщик контура для view по умолчанию получает контур из фона view. Чтобы view не отбрасывала тень, установите значение поставщика контура в null.

Обрежьте view

Обрезание позволяет вам легко изменить форму view. Вы можете обрезать view для совместимости с другими элементами дизайна или изменить форму view в ответ на действия пользователя. Вы можете обрезать view к его области контура с помощью метода View.setClipToOutline() или используя атрибут android:clipToOutline. Только прямоугольник, круг и скругленный прямоугольник поддерживают обрезание, как определено в методе Outline.canClip().

Чтобы обрезать view в форме drawable, установите drawable в качестве фона view (как показано выше), и вызовите метод View.setClipToOutline().

Обрезание view это дорогостоящая операция, поэтому не анимируйте форму, которую вы используете чтобы обрезать view. Для достижения этого эффекта используйте Reveal эффект анимации.

Всем выйти из сумрака: как добавить тень на Android

Когда заходит речь про тени на Android, возникает сразу несколько вопросов. Первый: зачем они нужны? Второй: почему нельзя использовать системные тени и жить счастливо? Третий: если нельзя использовать системные тени, как реализовать кастомные?

Это Сергей Петров, Android-разработчик в команде Design System inDrive, и вместе мы поговорим о тенях на Android.

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

Искренне надеюсь, что ваша стойкость и убедительность позволит вам и дальше использовать elevation для отрисовки теней. Если нет — придется искать ответ на третий вопрос.

Оговорюсь, что изначально я пробовал подобрать нужные значения параметров для системных теней. В Android, начиная с API 21, доступны атрибуты темы ambientShadowAlpha и spotShadowAlpha. С помощью них можно регулировать глобальные настройки прозрачности теней.

А позже в API 28 добавили возможность настраивать цвета теней через атрибуты темы outlineAmbientShadowColor и outlineSpotShadowColor, а также свойства View — outlineAmbientShadowColor и outlineSpotShadowColor.

Elevation

Попробуем подобрать подходящий elevation и прозрачность тени, и посмотрим, что из этого получится.

У нас в дизайне есть три разновидности теней (представлены на картинке ниже):

  • S — размер 12dp.
  • M — размер 20dp.
  • L — размер 32dp.

У каждой тени свои настройки прозрачности и смещения по оси Y. На смещение мы влиять не можем, но хотя бы попробуем подобрать значения прозрачности. Сложность в том, что до API 28 эти значения глобальны в рамках темы. Задать разным по стилю теням разные прозрачности, как в дизайне, возможности нет. К тому же, цвет тени в дизайне не черный, как в дефолтном Android. Что ж, попробуем добиться хотя бы примерного сходства.

Долго и усердно подбираем значения, примерно подходящие всем трем теням сразу.

// тема 0.01 0.08 // настройки elevatiom 12dp 24dp 30dp

Тень S — дизайнТень S — elevation 8dpТень M — дизайн Тень M — elevation 24dp Тень L — дизайнТень L — elevation 30dp

Кажется, получается довольно неплохо. Тень S слегка отличается, но две другие выглядят сносно. Настроить точнее при помощи общих настроек прозрачности вряд ли получится, но, начиная с API 28, можно получить совсем точное совпадение.

Получается, задача решена, и можно убеждать дизайнера, что тень S будет слегка отличаться от дизайна. В конце концов, пользователь вряд ли это заметит, да и кто вообще из пользователей Android обращает внимание на такие мелочи.

Но оказалось, что не все так просто. В Android два источника света: ambient light, который светит во все стороны, и key light — светит направленно. Кому интересно, в этой статье очень хорошо и с картинками раскрыта эта тема.

Источники света в Android

Тот, что светит сверху под углом, и есть key light. Он дает ярко выраженную тень в нижней части объекта. И вот, что происходит с тенью, особенно при больших elevation , по мере отдаления от верхней части экрана.

Тень L — элемент в верхней части экрана Тень L — элемент в нижней части экрана

Как в жизни: чем дальше от источника света, тем длиннее тень. Можно ли на это повлиять? В данной статье в разделе Don’t try this at home утверждается, что да, но у меня не получилось. Но даже если бы и получилось, и на код-ревью закрыли глаза на этот очевидный хак, это не решило проблемы полностью. Где бы не размещался источник света, тени в любом случае были бы неравномерными. Причина тому — большой elevation , необходимый для достижения нужного эффекта.

Изрядно расстроившись, переходим к плану Б — рисовать тень самостоятельно.

MaterialShapeDrawable

Раз не получилось с elevation , попробуем другой бесплатный метод. Вспоминаем, что в Material библиотека имеет поддержку теней и на античных устройствах. Давайте посмотрим на реализацию.

Заглядываем внутрь MaterialShapeDrawable и видим, что они на пару с неким ShadowRenderer занимаются интересными вещами. По заданным параметрам формы тень отрисовывается при помощи шейдеров LinearGradient и RadialGradient. То есть, тень — это градиент вокруг формы.

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

val shape = ShapeAppearanceModel.builder() .setAllCornerSizes(16.toPx()) .build() val drawable = MaterialShapeDrawable(shape) drawable.fillColor = ColorStateList.valueOf(Color.WHITE) drawable.shadowVerticalOffset = 8.toPx() drawable.shadowRadius = 32.toPx() drawable.shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS background = drawable

Тень MaterialShapeDrawable

Кажется, работает, но есть пару негативных моментов. Во-первых, настройки прозрачности градиента зашиты в ShadowRenderer. Чтобы сделать тень по своим параметрам, придется собирать код, разбросанный по нескольким классам, и копировать в проект.

Во-вторых, производительность решения оставляет желать лучшего. Ради интереса решено было повесить на вьюшку аниматор, который менял ее размер, и посмотреть, как будет работать отрисовка. Лаги, даже на релизной сборке, были заметны невооруженным глазом, что подтвердил и systrace .

Время отрисовки кадра — 18 миллисекунд

Время отрисовки одного кадра — 18 миллисекунд. Это только draw одной вьюшки на экране. А draw — довольно частая операция ��

Пожалуй, поищем другое решение. Но, стоит сказать, что идею с Drawable и отрисовкой тени за пределами собственных границ View я взял именно здесь.

Как еще нарисовать тень

В процессе поиска ответа на этот вопрос нашлись еще 3 способа, помимо указанных выше.

  1. Paint.setShadowLayer — самый простой и понятный. Минимум кода, отлично работает при наличии аппаратного ускорения (что для современных устройств — стандарт).
  2. BlurMaskFilter — второй по простоте, чуть больше кода, работает также отлично.
  3. ScriptIntrinsicBlur — пожалуй, еще сложнее, также смущает статус Deprecated и рекомендации по миграции.

Есть еще экзотический способ, у меня он не завелся, и я так до конца и не понял, как это работает. Если заведете и разберетесь, напишите в комментариях.

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

Анализ требований

Допустим, со способом определились. Теперь сформулируем, что в итоге хотим получить. В идеальном мире это должно быть также удобно, как указать elevation .

Размер тени не должен влиять на размер и позицию View на экране. Представляете, каково подбирать отступы или центрировать View , на размеры которой влияет отбрасываемая ей тень? Наличие тени не должно влиять на верстку существующих экранов.

Кроме того, должна быть возможность указать параметры тени в XML (в верстке или в стиле) и, что немаловажно, увидеть результат в превью Android Studio.

Еще нужно уметь отрисовать тень у любых View , вне зависимости от того, есть ли у них фон или elevation . И совсем хорошо, если время отрисовки не будет занимать весь фрейм.

Также определимся, что тень у нас отбрасывает только простая форма: прямоугольник (со скругленными углами или без), овал, и возможно модный squircle, если не удастся отговорить дизайнеров.

Суммарно все это звучит, как необходимость наследоваться от нужных View и расширять их функционал. При этом логику построения и отрисовки тени можно вынести в общий вспомогательный класс. Так реализация тени в компонентах превратится в тривиальную задачу.

  • Создадим Drawable , умеющий рисовать тень определенной формы.
  • Напишем View , использующий этот Drawable .
  • Измерим производительность решения.

NinePatchDrawable

В одной замечательной статье про тени на Android от этой идеи отказались. Статья действительно замечательная, но почему-то не попалась мне на глаза в тот момент, когда я искал решение.

Итак, что такое 9-patch и зачем он нужен? Тут мне, как старому разработчику игр на Marcomedia Flash (да упокой Господь его душу вместе с душой Стива), нужно смахнуть ностальгическую слезу. Эту технику я впервые повстречал там, а «Википедия» утверждает, что именно там она впервые и была придумана.

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

Таким образом, экономится и память (размер Bitmap минимален), и процессорное время (отдать Bitmap на отрисовку почти ничего не стоит). Ну а GPU только дай Bitmap порисовать.

К счастью, Android дает возможность разработчикам создавать NinePatchDrawable программно. А поскольку и форма фигуры и параметры тени известны — задача тривиальная.

Реализация

Определим параметры и форму тени:

data class ShadowSpec( @ColorInt val shadowColor: Int = Color.TRANSPARENT, @Px val shadowOffsetX: Float = 0f, @Px val shadowOffsetY: Float = 0f, @Px val shadowSize: Float = 0f, val cornerSize: CornerSize? = null, val cornerSizeTopLeft: CornerSize? = null, val cornerSizeTopRight: CornerSize? = null, val cornerSizeBottomLeft: CornerSize? = null, val cornerSizeBottomRight: CornerSize? = null )

Для построения формы было решено использовать ShapeAppearanceModel, а также ShapeAppearancePathProvider, который на основе этой модели построит Path, используемый для конечной отрисовки.

В коде это выглядит так:

// строим форму - спасибо исходникам Material val path = Path() val provider = ShapeAppearancePathProvider() val model = ShapeAppearanceModel.Builder() .setTopLeftCorner(CornerFamily.ROUNDED, topLeftCornerSize) .setTopRightCorner(CornerFamily.ROUNDED, topRightCornerSize) .setBottomLeftCorner(CornerFamily.ROUNDED, bottomLeftCornerSize) .setBottomRightCorner(CornerFamily.ROUNDED, bottomRightCornerSize) .build() provider.calculatePath(model, 1f, RectF(0f, 0f, width, height), path)

Форма есть, теперь посчитаем, сколько займет тень — радиус тени плюс смещение. Есть еще параметр SHADOW_SPREAD_MULTIPLIER , чуть увеличивающий область для того, чтобы все непрозрачные пиксели поместились в итоговый Bitmap .

// на глаз подбираем размер тени при размытии так, // чтобы все непрозрачные пиксели отрисовывались в итоговой области. with(spec)

Минимально необходимый же размер Bitmap считается как радиусы скругления углов формы плюс размер самой тени. Границы 9.patch тоже считаются тривиально.

// определяем границы углов val left = max(topLeftCornerSize, bottomLeftCornerSize) val top = max(topLeftCornerSize, topRightCornerSize) val right = max(topRightCornerSize, bottomRightCornerSize) val bottom = max(bottomLeftCornerSize, bottomRightCornerSize) // минимальный размер исходя из формы, с некоторым запасом val width = max(left + right, dp20) + 2 * dp1 val height = max(top + bottom, dp20) + 2 * dp1 // размер Bitmap val bitmapWidth = width + spreadBounds.left + spreadBounds.right val bitmapHeight = height + spreadBounds.top + spreadBounds.bottom // области для 9.patch val leftChunk = left + spreadBounds.left val topChunk = top + spreadBounds.top val rightChunk = bitmapWidth - right - spreadBounds.right val bottomChunk = bitmapHeight - bottom - spreadBounds.bottom

Приступим к отрисовке. Мы используем Paint.setShadowLayer и после вырезаем форму, оставляя земле лишь тень на случай, если элемент с тенью решит стать полупрозрачным.

// готовим инструменты для отрисовки val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply < color = spec.shadowColor setShadowLayer(spec.shadowSize, spec.shadowOffsetX, spec.shadowOffsetY, spec.shadowColor) >val clearPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply < xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) >val matrix = Matrix() matrix.postTranslate(spreadBounds.left, spreadBounds.top) path.transform(matrix) // отрисовываем форму с тенью и вырезаем саму форму canvas.drawPath(path, paint) canvas.drawPath(path, clearPaint)

На выходе получаем такую аккуратную картинку. Зелеными линиями на ней показана сетка, в соответствии с которой будет происходить растяжение. C помощью этой Bitmap можно отрисовать форму любого размера с тенью S и углами 16dp.

Bitmap для NinePatchDrawable

Остается лишь «запечатать» ее в NinePatchDrawable . API не самый простой, но StackOverflow не бросит в трудную минуту.

// строим drawable drawable = NinePatchDrawable( context.resources, NinePatchUtils.getNinePatch( bitmap = bitmap, left = leftChunk.roundToInt(), top = topChunk.roundToInt(), right = rightChink.roundToInt(), bottom = bottomChunk.roundToInt() ) )

Использование

Поместив всю реализацию в класс ShadowRenderer на 200 строк, можем создать ShadowView и попробовать его в действии. Тут нужно обратить внимание на три момента.

Во-первых, придется отключить outlineProvider для того, чтобы убрать нативную тень, которую дает elevation. Сам elevation мы хотим сохранить по понятным причинам. К тому же, outlineProvider не позволит нам нарисовать тень за пределами собственных границ ShadowView , если вдруг включить clipToOutline .

Во-вторых, нужно отключить clipChildren у родительского контейнера — тень мы хотим снаружи, а не внутри границ View .

Тень обрезается границами View

Третий момент обнаружился, когда я попробовал применить к ShadowView полупрозрачность (для наглядности сделаю тень красной).

Оказалось, что при alpha меньше единицы клиппинг у View включается автоматически, обрезая тень. Но тут я отделался легким испугом. Достаточно было прочитать документацию к методу View.setAlpha и опять обрести душевный покой.

Starting with Build.VERSION_CODES.M , setting a translucent alpha value will clip a View to its bounds, unless the View returns false from hasOverlappingRendering() .

Посмотрим на наш ShadowView . За минусом конфигурации получаем 3 метода.

override fun hasOverlappingRendering(): Boolean < // по умолчанию View не отрисовывает за своими границами, // если alpha < 1 (см setAlpha) // переопределяем это поведение, если есть видимая тень return !shadowSpec.isShadowVisible >override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) < super.onSizeChanged(w, h, oldw, oldh) shadowRenderer.setSize(w, h) >override fun draw(canvas: Canvas)

Конечно, методов там чуть больше. Есть еще автоматическое отключение chipChildren у контейнера, отключение outlineProvider , установка параметров тени из стиля/программно — все то, что мы с вами так любим писать в кастомных вьюшках. Но в действительности процесс создания компонента с тенью выглядит просто.

Производительность

Я был практически уверен, что в этом отношении проблем не возникнет по причинам, описанным выше. Так и произошло.

Время отрисовки кадра — 1 миллисекунда

Сценарий тот же самый, что и с MaterialShapeDrawable : один ShadowView на экране и аниматор, меняющий его размеры.

Естественно, процесс создания Bitmap тоже не бесплатен, но визуально в трейсе найти его не удалось — все фреймы коротенькие и ровненькие, как на подбор. Ставить в код метку для трейса и выяснять точное количество миллисекунд было уже лень.

Но есть и ложка дегтя. Поскольку у объектов круглой формы скругления углов зависят от ширины или высоты, то изменение размера влечет за собой пересоздание Bitmap и NinePatchDrawable . Тогда картина заметно ухудшается, в районе 20мс на фрейм.

Вариантов решения два. Первый — пропускать генерацию Bitmap и рисовать тень на канвасе с помощью Paint напрямую в каждом вызове draw . Второй — указать большие значения радиуса углов, достаточные для отрисовки овала. Так получим большую исходную Bitmap , но зато отрисовка останется мгновенной.

Можно еще улучшить производительность, использовав LruCache. Разновидностей тени у нас всего 3, форм тоже немного. Поэтому хранение и использование уже сгенерированных Bitmap повторно реализовать достаточно просто. Но до этого еще не дошли руки, да и пока не было необходимости.

Заключение

Стоила ли игра свеч? Определенно да. Тени стали выглядеть гораздо лучше, чем стандартный elevation .

Можно ли использовать стандартный elevation ? Тоже да, но лучше избегать больших значений. Настройки прозрачности могут дать неплохие результаты.

Нужно ли писать свое собственное решение, когда есть много библиотек? Думаю, тоже да. Так вы получите именно то, что нужно вам. Тем более, Android почти все дает из коробки, поэтому решение получится компактным.

Что с Compose? С Compose все будет хорошо, скоро, скоро.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *