Как закруглить углы у imageview android

Скругление углов в Android. Быстро, качественно, недорого.

Привет, меня зовут Дмитрий @dmitriy1984g , я больше трех лет разрабатываю android-приложения в калининградской компании KODE.

Довольно часто при разработке мобильных приложений требуется придавать содержимому некую форму, например, сделать круглое изображение для аватарки. Если для статичных элементов (ImageView) можно обойтись таким инструментом как Picasso transformation, то для динамического содержимого дела обстоят сложнее. Например, есть список элементов, который можно прокручивать и нужно закруглить края. Для этого вполне может подойти виджет CardView.

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

Статья длинная — для тех, кому лень читать всё, самое важное и исходный код в конце статьи.

Дизайнерская верстка

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

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

Протоптанная дорожка

На первый взгляд, можно использовать виджет CardView, но в процессе разработки был выявлен ряд недостатков:

  • нельзя задать радиус для каждого угла отдельно;
  • “честное” закругление работает только на версиях, начиная с Android Lollipop. До этой версии добавляется отступ содержимого на размер радиуса углов. Это заметно при прокрутке, появляются пробелы между краями карточки;
  • при установке нулевого радиуса приложение, запущенное на устройстве с версией ниже Andrid Lollipop, падает. В документации такие случаи просто не описаны.

Тернистый путь

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

Критерии для реализации инструмента:

  • возможность использовать в версиях Android до Lollipop и после. В крайнем случае комбинировать подходы;
  • возможность задавать любую форму. Например, с помощью класса Path;
  • наличие плавных линий и возможности сглаживания (antialiasing);
  • и конечно же, производительность. Придание формы содержимому должно происходить с минимальным воздействием на производительность устройства, особенно для динамически меняющегося содержимого и формы.

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

Я попробовал 4 различных варианта:

  • Свойство View.clipToOutline
  • Метод Canvas.clipPath() при отрисовке дочерних элементов в контейнере
  • Метод Canvas.drawPath() при отрисовке дочерних элементов в контейнере. Настройки PorterDuff.Mode + View.LAYER_TYPE_SOFTWARE
  • Метод Canvas.drawPath() при отрисовке дочерних элементов в контейнере. Настройки PorterDuff.Mode + View.LAYER_TYPE_HARDWARE

Свойство View.clipToOutline

Этот подход используется в виджете CardView. Глянем, что он умеет.

Как это работает:
Задаем фоновое изображение нужной формы и выставляем в поле View.clipToOutline значение “true”.
Но мы знаем, что работаем с API Android, и слишком легко, чтобы быть правдой. Смотрим документацию метода View.setClipToOutline()

Sets whether the View’s Outline should be used to clip the contents of the View…Note that this flag will only be respected if the View’s Outline returns true from Outline.canClip()

Всего-то достаточно, чтобы Outline.canClip() возвращал true . Смотрим документацию и для этого метода:

Returns whether the outline can be used to clip a View. Currently, only Outlines that can be represented as a rectangle, circle, or round rect support clipping.

Получается, что пока доступен ограниченный набор форм. Вот так, задумка хорошая, но возможности не позволяют реализовать весь полет фантазий.
Для того, чтобы динамически менять форму фонового изображения, можно создать класс, наследованный от Drawable. В методе getOutline() задаем нужный контур.

  • Хорошая производительность;
  • Сглаживание линий.
  • Работает только на Lollipop и выше;
  • Ограничение форм: прямоугольник, овал и прямоугольник со скругленными краями.

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

Метод Canvas.clipPath() при отрисовке дочерних элементов в контейнере

Данный подход состоит в наследовании от класса контейнера. В методе dispatchDraw() делаем обрезку по нужной форме с помощью метода Canvas.clipPath()

Читайте также:  Как получить размер экрана android studio

Все что нужно — это задать необходимый контур с помощью объекта класса Path . Недочеты видны после запуска приложения — отсутствует сглаживание линий (antialiasing), а как его добавить — непонятно.

  • Работает на версиях до/после Lollipop;
  • Хорошая производительность.

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

Метод Canvas.drawPath() при отрисовке дочерних элементов в контейнере, View.LAYER_TYPE_SOFTWARE

Этот подход также требует наследования от контейнера. В методе dispatchDraw() делаем обрезку по нужной форме с помощью метода Canvas.drawPath() , в который передаем контур и объект класса Paint с инициализацией свойства xfermode .

Объект PorterDuffXfermode(PorterDuff.Mode.CLEAR) позволяет вырезать нужную область на экране, используя режим наложения одного изображения на другое. Зачем тогда вызов setLayerType(View.LAYER_TYPE_SOFTWARE, null) ? Дело в том, что этот режим некорректно работает в других типах Layer. В ином случае за обрезанной фигурой будет белый фон, а нам нужен прозрачный.
Подробнее про PorterDuff в документации .

Соответственно, данная настройка ведет к падению производительности. Но насколько это критично, видно на графике производительности.

В процессе тестирования выяснилось, что перестают работать такие вещи как тень у кнопки. Похоже, проблема именно в View.LAYER_TYPE_SOFTWARE.

  • Работает на версиях до/после Lollipop;
  • Гладкие, плавные линии.
  • Плохая производительность;
  • Не отображается тень на кнопках. Похоже, проблема именно в View.LAYER_TYPE_SOFTWARE;
  • Работают не все виды PorterDuff.Mode.

Метод Canvas.drawPath() при отрисовке дочерних элементов в контейнере, View.LAYER_TYPE_HARDWARE

Ключевым моментом, как и в предыдущем подходе, является переопределение метода dispatchDraw() , в котором используем метод canvas.drawPath() для обрезки. Для корректной работы PorterDuffXfermode при настройке View.LAYER_TYPE_HARDWARE нужно дописать дополнительную логику с объектом Canvas в методе dispatchDraw() .За подсказку спасибо Ilya Nekrasov

Как это выглядит в реализации:

  • Работает на версиях до/после Lollipop;
  • Хорошая производительность;
  • Гладкие, плавные линии.
  • Иногда происходит мерцание, если в контейнере два View и у каждого меняется Alpha, т.е. один объект появляется, другой исчезает. При этом объект, который исчезает, может на доли секунды появляться при 100% Alpha. Мерцание не замечено в версии Android до Lollipop.

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

Схематично было так:

Данный подход может быть применен для большинства задач. Учитывая, что в объекте Path можно задать любую форму, это дает практически безграничные возможности. Радует и относительно хорошая производительность. Но из-за дополнительных манипуляций с объектом Canvas , могут возникать побочные эффекты, как это произошло у нас.

Extra

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

Метод PorterDuff.Mode + View.LAYER_TYPE_HARDWARE

Метод Canvas.clipPath()

Мнение автора

Каждый из подходов хорошо применим в определенной ситуации.

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

Интересный подход с методом Canvas.clipPath() . Минимум кода для реализации, менее “прихотлив” к версии Android, можно задавать любую форму. Если бы можно было добавить Antialiasing, то это было бы практически идеальным решением.

Что не советовал бы использовать, так это метод с View.LAYER_TYPE_SOFTWARE PorterDuffXfermode — сплошные минусы.

И как более универсальное средство — это реализация с помощью View.LAYER_TYPE_HARDWARE и PorterDuffXfermode . Данное решение работает на всех версиях Android, довольно хорошая производительность, можно задать любую форму контура, но нужно помнить об предостережениях, описанных выше.

В целом, результат исследования меня не особо обрадовал. Может быть я слишком требователен. 😀 Да, мы нашли способ сделать то, что было задумано, но хотелось бы более простого решения, так как наследование не самый лучший вариант. И к тому же нужен более удобный в использовании метод, как в случае с View.clipToOutline . Возможно, в будущем Android API этим нас обрадует. Ну или, может, это проще сделать в Flutter и пора переходить на него?🙂

Ух, это была довольно большая статья. Кто дочитал, тому виртуальный приз 🍬

Ссылка на исходный код . Тестовое устройство: Xiaomi Redmi 4x

Читайте также:  Assassins creed brotherhood для андроид

Удачного кода, меньше багов, больше счастливых пользователей!

Источник

Как сделать ImageView с закругленными углами?

В Android ImageView по умолчанию представляет собой прямоугольник. Как сделать прямоугольник с закругленными углами (обрезать все 4 угла моего растрового изображения, чтобы получились прямоугольники с закругленными углами) в ImageView?

30 ответов

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

Это не мой код, но я его использовал, и он прекрасно работает. Я использовал его в качестве помощника в классе ImageHelper и немного расширил его, чтобы передать степень растушевки, которая мне нужна для данного изображения.

Окончательный код выглядит так:

Надеюсь, это кому-то поможет!

Хотя приведенный выше ответ работает, Ромен Гай (основной разработчик Android) показывает лучший метод в его блоге, который использует меньше памяти за счет использования шейдера, а не создания копии растрового изображения. Общая суть функциональности здесь:

Преимущества этого метода перед другими в том, что он:

  • не создает отдельную копию растрового изображения , которое использует много памяти для больших изображений [по сравнению с большинством других ответов здесь]
  • поддерживает сглаживание [против метода clipPath]
  • поддерживает альфа [по сравнению с методом xfermode + porterduff]
  • поддерживает аппаратное ускорение [по сравнению с методом clipPath]
  • только выводится на холст один раз [по сравнению с методами xfermode и clippath]

Я создал RoundedImageView на основе этого кода, который включает эту логику в ImageView и добавляет правильные ScaleType поддержка и необязательная закругленная граница.

Просто сделайте это:

  • Создайте округлую фигуру, которую можно рисовать, примерно так:

Рез / вытяжка / round_outline.xml

  • Установите объект для рисования в качестве фона вашего ImageView: android:background=»@drawable/round_outline»
  • Согласно этой документации, все, что вам нужно сделать, это добавить android:clipToOutline=»true»

К сожалению, есть ошибка, и этот атрибут XML не распознается. К счастью, мы все еще можем настроить отсечение в Java:

  • В вашей активности или фрагменте: ImageView.setClipToOutline(true)

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

Примечание.

Этот метод работает для любой фигуры, которую можно рисовать (а не только с закругленными углами). Он будет обрезать ImageView по любому контуру формы, который вы определили в своем Drawable xml.

Особое примечание об ImageViews

setClipToOutline() работает только тогда, когда фон представления установлен на фигуру, которую можно рисовать. Если эта фоновая фигура существует, View обрабатывает контур фигуры как границы для целей обрезки и затенения.

Это означает, что если вы хотите использовать setClipToOutline() для закругления углов в ImageView, ваше изображение должно быть установлено с использованием android:src вместо android:background (поскольку фон должен быть установлен в соответствии с закругленной формой) . Если вы ДОЛЖНЫ использовать фон для установки изображения вместо src, вы можете использовать этот обходной путь:

  • Создайте макет и установите его фон в соответствии с вашей фигурой, которую можно рисовать
  • Оберните этот макет вокруг вашего ImageView (без отступов)
  • ImageView (включая все остальное в макете) теперь будет отображаться с закругленной формой макета.

В версии 21 библиотеки поддержки теперь есть решение этой проблемы: оно называется RoundedBitmapDrawable.

Это в основном похоже на обычный Drawable, за исключением того, что вы задаете ему радиус угла для отсечения с помощью:

Итак, начиная с Bitmap src и цели ImageView , это будет выглядеть примерно так:

Начиная с версии 1.2.0-alpha03 материала Библиотека компонентов — это новый ShapeableImageView .

Вы можете использовать что-то вроде:

С помощью jetpack compose 1.0.0 (протестированного с 1.0.0-beta08 ) вы можете применить clip Modifier с использованием RoundedCornerShape :

Быстрое решение xml —

Вы можете установить желаемую ширину, высоту и радиус в CardView и scaleType в ImageView.

В AndroidX используйте

Я сделал Custom ImageView:

Вывод:

Надеюсь, это поможет тебе.

Я обнаружил, что оба метода очень помогли найти рабочее решение. Вот моя составная версия, которая не зависит от пикселей и позволяет иметь несколько квадратных углов, а остальные углы имеют тот же радиус (что является обычным вариантом использования). Благодаря обоим вышеперечисленным решениям:

Кроме того, я переопределил ImageView, чтобы вставить это, чтобы я мог определить его в xml. Вы можете добавить сюда часть логики, которую выполняет супервызов, но я прокомментировал это, так как в моем случае это бесполезно.

Надеюсь это поможет!

Котлин

Чтобы создать ImageView Циркуляр, мы можем изменить cornerRadius с помощью:

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

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

[править] Наложите рамку на исходное изображение, например, используя FrameLayout . Первым элементом FrameLayout будет изображение, которое вы хотите отобразить скругленным. Затем добавьте еще один ImageView с рамкой. Второй ImageView будет отображаться поверх исходного ImageView , и поэтому Android отобразит его содержимое над исходным ImageView .

Читайте также:  Ancient empires для андроид

Подсказка Джорджу Уолтерсу II выше, я просто взял его ответ и немного расширил его, чтобы поддерживать скругление отдельных углов по-разному. Это можно было бы еще немного оптимизировать (некоторые из целевых рек перекрываются), но не очень сильно.

Я знаю, что эта ветка немного устарела, но это один из лучших результатов по запросам в Google о том, как скруглить углы ImageViews на Android.

Ромен Гай — вот где это.

Уменьшенная версия выглядит следующим образом.

Примените форму к своему imageView , как показано ниже:

Это может быть полезно для вас, друг.

ИЗМЕНИТЬ

Вот краткий ответ:

В папке / res / drawable создайте файл frame.xml. В нем мы определяем простой прямоугольник с закругленными углами и прозрачным центром.

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

Почему бы не сделать вырезку в draw() ?

Вот мое решение:

  • Расширить RelativeLayout с помощью отсечения
  • Поместите ImageView (или другие представления) в макет:

Ни один из методов, представленных в ответах, у меня не работал. Я обнаружил, что следующий способ работает, если ваша версия Android 5.0 или выше:

Никакие формы xml не должны определяться, и приведенный выше код создает углы только для вершины, что обычные методы не будут работать. Если вам нужно скруглить 4 угла, удалите:

Из параметра для нижней части в setRoundRect. Вы можете расширить фигуру до любой другой, указав контуры, которые соответствуют вашим потребностям. Ознакомьтесь со следующей ссылкой:

Обратите внимание, что, как и в случае с любой другой мерой в Android, вы должны «конвертировать» размер, как правило, из DP. В приведенном выше примере, скажем, вы хотите, чтобы радиус был 24

Например, вы можете позже добавить границу в чертеж с радиусом, установленным как «24», и вы хотите, чтобы она совпадала. Следовательно,

Следующий код создает объект макета прямоугольника с закругленными углами, который рисует прямоугольник с закругленными углами вокруг всех размещенных в нем дочерних объектов. Он также демонстрирует, как создавать представления и макеты программно без использования XML-файлов макета.

Класс для объекта макета RoundedRectangle определен здесь:

Если вы используете библиотеку Glide, это будет полезно:

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

Если ваше изображение находится в Интернете, лучше всего использовать скольжение и RoundedBitmapDrawableFactory (из API 21, но доступно в библиотеке поддержки), например:

В макете можно использовать только ImageView , а с помощью glide можно применять скругленные углы с помощью этого метода.

первый в вашем Gradle напишите,

для изображения с закругленными углами

метод вызова:

Просто нужно использовать androidx.cardview.widget.CardView

Код

Ответ на вопрос, который перенаправлен сюда: «Как создать круговой ImageView в Android?»

Другой простой способ — использовать CardView с угловым радиусом и ImageView внутри:

Закругленное изображение. Используется ImageLoader здесь

Или вы можете использовать библиотеку Picasso от Square.

Вы можете скачать файл RoundedTransformation здесь здесь

Поскольку все ответы казались мне слишком сложными только для углов, я подумал и пришел к другому решению, которым, как мне кажется, стоит поделиться, просто с XML, если у вас есть место вокруг изображения:

Создайте фигуру с рамкой и прозрачным содержимым, например:

Затем в RelativeLayout вы можете сначала разместить свое изображение, а затем в том же месте над фигурой с другим ImageView. Форма обложки должна быть больше по размеру на величину ширины границы. Будьте осторожны, выбирайте больший угловой радиус, поскольку внешний радиус определен, но внутренний радиус — это то, что покрывает ваше изображение.

Надеюсь, это тоже кому-то поможет.

Изменить в соответствии с запросом CQM, относительный пример макета:

Моя реализация ImageView с виджетом со скругленными углами, который (вниз || вверх) изменяет изображение до требуемых размеров. Он использует кодовую форму CaspNZ.

В последнее время есть другой способ — использовать созданный API Glide. Это требует некоторой начальной работы, но затем дает вам всю мощь Glide с гибкостью, чтобы делать что угодно, потому что вы пишете фактический код, поэтому я думаю, что это хорошее решение на долгую перспективу. Кроме того, использование очень простое и удобное.

Сначала настройте Glide версии 4+:

Затем создайте класс модуля приложения Glid, чтобы запустить обработку аннотации:

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

После добавления этих файлов соберите свой проект.

Затем используйте его в своем коде так:

Существует классная библиотека, которая позволяет формировать просмотры изображений.

Источник

Оцените статью