Widget android rounded corners

Скругление углов в 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()

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

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

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

Читайте также:  Real boxing для андроид кэш

Метод 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

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

Источник

How to Create Rounded Corners for a Button in Android

Making buttons in your Android app look nice is an important part of the user experience. As you design and build your app you are likely to eventually want to create rounded corners for your buttons.

Android offers a default button that is not always intuitive to style the way you want, in many cases you will even need to use drawables to get the look and feel you are after.

To create a rounded corners for a button in Android you will need to:

  1. Create a Drawable for your Button
  2. Add a Rectangle Shape to your Drawable
  3. Set the Background Color in your Drawable
  4. Set the Radius of the Corners in your Drawable
  5. Apply the Drawable to your Button
  6. Add a Button to your Activity / Fragment Layout and Apply the Drawable to the Button

In this post I have included code samples for creating a button with rounded corners in Android which you can make use of in your Android app.

Читайте также:  Андроид подключение usb устройств

In addition to this I have included sections in this post about styling rounded buttons in different ways such as:

  • Applying a solid background color to a rounded button
  • Applying a gradient background color to a rounded button
  • Applying a shadow to a rounded button
  • Creating a circular button

Creating a Button with Rounded Corners in Android

In this section of the post I will show you how to create a button with rounded corners in Android that looks like the image below.

First you will need to create a new drawable resource inside the “res/drawable” directory.

To create a drawable in Android Studio, expand the “res/drawable” directory and right click on the “drawable” folder. Hover over the “New” menu and select the “Drawable resource file” menu item.

Enter a file name for the new drawable and select the “OK” button. This will create the drawable resource file.

For this button we will be applying a solid background color to it. There is a resource file for centralizing of all the colors in your Android app located in the “res/values” directory called colors.xml, where you can add the background color of the button.

Colors are defined using a hexadecimal value, if you are interested in a useful free tool to help you locate the hexadecimal value for a color check out htmlcolorcodes.com in your web browser.

Edit the colors.xml file in Android Studio and add a new color to be used for the rounded button’s background color. See the code snippet below showing how colors are defined inside the colors.xml file.

Now we have everything we need to create the drawable resource for the button with rounded corners. Open up the drawable resource you have previously creating in Android Studio.

Replace the code inside your drawable resource with the code snippet below. Make sure the “android:color” attribute references the correct name of the color that you created in your colors.xml file.

Inside the drawable resource you will have a shape element, Android supports 4 different types of shape drawables rectangle, oval, line and ring. We will use the rectangle shape drawable for our button with rounded corners by setting the “android:shape” attribute to “rectangle” on the shape element.

Inside the shape element, we will add two elements, a solid element and a corners element.

The solid element is used for setting the background color of the button to be filled with a single solid color. Use the “android:color” attribute on the solid element to set the fill color of the shape to the button color we previously added to the colors.xml file.

The corners element is used to apply a specific radius to the corners of the rectangle shape to create round corners. The radius for the corners are defined in display pixels (dp).

You can apply the radius to individual corners one at a time using the “android:topLeftRadius”, “android:topRightRadius”, “android:bottomLeftRadius”, “android:bottomRightRadius” attributes. Alternatively you can apply the same radius to all corners of the rectangle using the “android:radius” attribute.

If you want to learn more about shape drawables there is a useful developer guide available on developer.android.com that covers different drawable resources including shape drawables.

This will conclude the creation of the drawable, the next and final step is to add the Button to your Activity or Fragment. Open your Activity or Fragment layout resource in Android Studio.

Then you can add a Button to the layout of your Activity or Fragment. When adding your Button use the “android:background” attribute to apply your drawable to the Button. Also use the “android:padding” attribute to give a margin between the text inside your Button and the borders of the Button to give it a better look and feel.

Adding a Gradient Background to a Button with Rounded Corners

In this section of the post I will show you how to apply a gradient button to a button with rounded corners in Android that looks like the image below.

We will be following the same steps from the section above for parts of this tutorial to create the button with rounded corners so make sure you have read the section above first.

First you will need to create a new drawable resource inside the “res/drawable” directory and give the drawable file a name of “round_gradient_button”.

Next we will need to add two colors to the colors.xml resource file located inside the “res/values” directory. These two colors will represent the start and end colors that we will be used for the gradient background.

A great online resource I used to come up with colors to use in the gradient is gradientbuttons.colorion.co. The tool has a great list of colors shown together in a gradient background that you can scroll through if you are having troubles coming up with colors that look nice together.

If you find colors you like, select the “Get CSS Code” button and copy the hexadecimal codes of the colors and use them in your colors.xml file

Once you have added two colors to your colors.xml resource file next you will need to modify the round_gradient_button drawable that you previously created.

Читайте также:  Android 10 rds что это значит

Replace the code inside your drawable resource with the code snippet below. Make sure the “android:startColor” and “android:endColor” attributes reference the correct name of the colors that you created in your colors.xml file.

The drawable resource is very similar to what we created in the previous section above, it uses a rectangle shape with a corners element with the radius attribute to define the rounded corners.

The main difference is the use of the gradient element rather than the solid element. The gradient element is used, as it’s name suggests, for defining the fill of the drawable as a gradient of colors rather than a single solid fill color.

In the code snippet I have set the “android:angle” attribute to 0, this means that the gradient will move from left to right. If you want the gradient to move from the bottom to top you need to set the angle to 90. The value of the angle must be a multiple of 45. I have included the image below that shows different angles for the gradient.

Also in the code snippet I have set the “android:startColor” and “android:endColor” attributes to the colors I created in the previous step of this section.

I have not included the following in the code snippet but you can also make use of the following attributes to control how the gradient is displayed.

  • “android:centerX” and “android:centerY” which take floating point values between 0 and 1.0 to control the relative center position of the gradient
  • “android:centerColor” which refers to a color that is shown between the start and end colors
  • “android:gradientRadius” which references the radius shown in the gradient which is only applicable if the gradient type is set to radial
  • “android:useLevel” which is to be set to true if the gradient is used as a level list drawable
  • “android:type” is the pattern used for the gradient, options include “linear” which is the default, “radial” and “sweep”. I have included examples of each in the screenshot below.

This will conclude the creation of the drawable, the next and final step is to add the Button to your Activity or Fragment. Refer to the code snippet below showing the Button being placed inside the main activities layout resource file.

Adding a Shadow to a Button with Rounded Corners

In this section of the post I will show you how to apply a shadow to a button with rounded corners in Android that looks like the image below.

We will be following the same steps from the first section of this post to create the button with rounded corners so make sure you have read the first section of the post prior to starting this.

First you will need to create a new drawable resource inside the “res/drawable” directory and give the drawable file a name of “round_shadow_button”.

Because I will be reusing existing colors, I will not go through the process adding new colors to the colors.xml resource file again.

Replace the code inside your drawable resource with the code snippet below. Make sure that both of the “android:color” attributes properly reference actual colors.

This drawable uses a layer list with two items inside it. The first item represents the shadow and the second item represents the button. The two items contain rectangular shapes with the same radius of the corners but with different colors. Each of the items are positioned differently using the “android:left”, “android:top”, “android:bottom” and “android:right” attributes to allow the shadow to be displayed behind the button.

The next and final step is to add the Button to your Activity or Fragment. Refer to the code snippet below showing the Button being placed inside the main activities layout resource file.

Creating a Circular Button

In this section of the post I will show you how to create a circular button in Android that looks like the image below.

First you will need to create a new drawable resource inside the “res/drawable” directory and give the drawable file a name of “circular_button”.

Because I will be reusing existing colors, I will not go through the process adding new colors to the colors.xml resource file again.

Replace the code inside your drawable resource with the code snippet below. Make sure that the “android:color” references an actual color.

The key stand out with this drawable is the use of the “oval” shape in the shape element’s “android:shape” attribute. Another key difference from the previous drawables referenced above is the use of the size element and providing a width and height of the drawable. This is to ensure the oval is large enough so that it is not stretched and appears as circle.

The next and final step is to add the Button to your Activity or Fragment. Refer to the code snippet below showing the Button being placed inside the main activities layout resource file.

Источник

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