Когда прямоугольное хочется сделать квадратным…
Андроид предлагает разнообразные средства управления размерами компонентов UI. Вы можете задать минимальную/максимальную ширину или высоту, можете сделать так, чтобы вид занимал все свободное пространство или был достаточного размера, чтобы отобразить все свое содержимое. Но иногда встроенной логики не хватает. В данной статье на примере ImageButton будет рассказано, как скорректировать размеры вида следуя собственным правилам.
Введение
Для определенности будем рассматривать конкретное задание: кнопки ImageButton должны быть определенной ширины (определяемой изображением), а высота их должна быть больше либо равна ширине. Пример как должно и не должно быть приведен на рисунках ниже.
правильно | неправильно |
![]() |
![]() |
Как обычно, есть несколько способов добиться такого поведения, некоторые из которых приведены ниже:
- можно задать кнопке свойство minHeight. Тогда высота кнопки будет не меньше заданной. Но сразу возникают проблемы: нужно заранее знать какой ширины изображение мы будем помещать на кнопку. Но это еще не самое худшее. Свойство minHeight задает общую высоту компонента без учета отступов, которые задаются для нашей кнопки с помощью NinePatch изображения. Следовательно, поменяли отступы – идем вручную рассчитывать и задавать значение minHeight, что совсем неудобно.
- можно перед тем как поместить изображение на кнопку узнать его ширину и высоту, а потом с помощью метода setLayoutParams, задать правильный размер кнопке, не забыв при этом учесть отступы. Нам потребуется написать существенное количество кода и не забывать его каждый раз вызывать. Плюс получаем сложности с созданием наших кнопок с помощью xml, так как недостаточно просто описать нашу кнопку на xml, нужно еще не забыть вызвать код для расчета правильных размеров. Сложно и запутанно.
- можно создать новый класс для кнопок и переопределить метод onMeasure, таким образом, чтобы он при рассчитывал ширину и высоту кнопки согласно нашим требованиям. Все будет работать автоматически и не повлечет за собой дополнительных накладных расходов. Кажется, это лучшее, что можно сделать.
Переопределяем onMeasure
Из документации узнаем, что метод onMeasure должен рассчитать размеры вида и передавать рассчитанные значения в метод setMeasuredDimension, тогда Андроид будет знать сколько места на экране отвести под конкретный вид. Узнать рассчитанные высоту и ширину вида можно с помощью функций getMeasuredHeight и getMeasuredWidth. Класс ImageButton уже имеет стандартную реализацию метода onMeasure, работа которого нас почти устраивает, за исключением того, что нам требуется, чтобы высота кнопки была не меньше ее ширины. Следовательно напрашивается следующий план действий:
- создаем новый класс производный от ImageButton
- переопределяем метод onMeasure
- вызываем стандартный метод onMeasure класса ImageButton
- с помощью функций getMeasuredHeight и getMeasuredWidth получаем рассчитанные значения ширины и высоты
- корректируем рассчитанную высоту и ширину кнопки, следуя нашей логике
- задаем новые размеры кнопки с помощью метода setMeasuredDimension
Приступаем к реализации
Создаем новый класс, назовем его SquareImageButton. Генерируем для нашего класса такие же конструкторы, как и у класса ImageButton, тогда мы сможем использовать новый класс так же, как будет это стандартный ImageButton.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SquareImageButton extends ImageButton { public SquareImageButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public SquareImageButton(Context context, AttributeSet attrs) { super(context, attrs); } public SquareImageButton(Context context) { super(context); } } |
Созданный нами класс пока функционирует точно так же как и ImageButton. Приступаем к добавлению нового функционала. Для этого переопределяем метод onMeasure и реализуем новую логику его работы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** * Рассчитывает размеры кнопки таким образом, что ее высота никогда не будет меньше * ее ширины. В остальном работает как метод onMeasure класса ImageButton */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // вызываем метод onMeasure класса ImageButton, чтобы расcчитать размеры // кнопки стандартным образом super.onMeasure(widthMeasureSpec, heightMeasureSpec); // сейчас наша кнопка имеет такие же размеры как если бы // она была экземпляром класса ImageButton // начинаем добавлять новую логику расчета размера // получаем рассчитанные размеры кнопки final int height = getMeasuredHeight(); // высота final int width = getMeasuredWidth(); // ширина // теперь задаем новый размер // ширину оставляем такую же как у стандартной кнопки // высоту выбираем как максимум между стандартной высотой и шириной setMeasuredDimension(width, Math.max(width, height)); } |
Новый метод onMeasure работает как же как и стандартный метод, за исключением того, что если рассчитанная высота кнопки меньше ее ширины, высота принимается равной ширине. Логика использования нового класса SquareImageButton в приложениях ничем не отличается от класса ImageButton, что очень удобно. Нужно лишь заменить ImageButton на SquareImageButton и все.
Смотрим, что получилось
Как было замечено, использование класса SquareImageButton не отличается от класса ImageButton, стоит лишь поменять имя, остальной код будет работать так же хорошо как и раньше. Поэтому приведу лишь один пример создания новых кнопок с помощью xml разметки.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.your_package_name.SquareImageButton android:id = "@+id/item_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/long_image" android:background="@drawable/selector_image_button"/> </FrameLayout> |
Получаем следующий результат:
стандартный ImageButton | SquareImageButton |
![]() |
![]() |
Созданный класс работает в точности, как и ожидалось. Высота кнопки рассчитывается с учетом ее ширины. Никакого дополнительного кода или заметного ухудшения производительности в предложенной реализации нет. Сохранена совместимость с классом ImageButton. В приложении нужно лишь поменять имя класса кнопок, чтобы добиться их правильного размера, весь остальной код останется работоспособным.
Если статья оказалась вам полезной или почему на сайте есть реклама
4 комментариев
Михаил Сообщает:
31.05.2013 на 6:47 дп (UTC 0 )
Спасибо. Очень полезная статья.
Есть ли какие-то нюансы при переопределении метода onMeasure для различных контейнеров? Например для ViewGroup. Мне необходимо динамически добавлять на компонент кастомные View разных размеров. Покопал, понял, что есть обязательный метод onLayout, который перерисовывает контейнер после добавления новых компонентов. но взаимосвязь мне их не понятна.
Iens Сообщает:
26.07.2013 на 12:22 пп (UTC 0 )
Спасибо! Очень доступно изложено!
Svyatozar Сообщает:
08.03.2014 на 4:38 пп (UTC 0 )
Спасибо большое, очень помогло
Yan Сообщает:
14.05.2014 на 6:08 пп (UTC 0 )
Просто и доступно,спасибо, очень хорошо объясняете=) пишите исчО