- Введение
- Специфические задачи, специфические view
- Создание custom view
- Добавление логики отображения
- Использование полученного view
- Добавление xml атрибутов
- Android курс в Технополисе 2019
- Custom View
- Жизненный цикл View
- Constructor
- onAttachedToWindow
- onMeasure
- onLayout
- onDraw
- Обновление View
- Иерархия
- Реализация Custom View-компонента в Android
- Рисуем!
- Constructor
- onAttachedToWindow
- onMeasure
- onLayout
- onDraw
- View Update
- Animation
Введение
Это будет серия постов на тему создания пользовательского view компонента для Android. Я покажу как нарисовать содержимое компонента, как работают layouting и measuring, как реализовать view groups и как анимировать содержимое компонента. В этом посте я объясню, как расширить стандартный view, как его использовать и как создать свои собственные xml атрибуты.
Специфические задачи, специфические view
Стандартные view компоненты, которые предоставляет Android, могут использоваться для многих задач и ситуаций. Тем не менее, в наших приложениях наибольшую часть кода часто занимает конфигурация этих view для специфических задач. Этот конфигурационный код часто находится в activity вашего приложения, поэтому их сложно содержать в чистоте. А при наличии большого количества различного кода возникают сложности и с его разделением в отдельные классы.
Предположим, вы работаете над приложением, которое содержит статистику тренировок пользователя. Например, общая дистанция, общее время и так далее, в зависимости от типа тренинга. Для того чтобы отображать данные в удобном виде, вы решили их адаптировать, основываясь на их продолжительности. Если это 2378 секунд, вы хотите отображать это время как «40 минут», а если это 18550 секунд, то лучше было бы отображать на дисплей «5 часов 9 минут».
Создание custom view
Один из способов решения этой задачи — создать специализированный view, который будет должным образом обрабатывать ваш текст. Давайте начнем с создания нашего собственного класса DurationTextView, который будет расширять класс TextView.
TextView, как и все view классы, имеет три различных конструктора: первый просто принимает контекст (Context), второй принимает контекст и набор атрибутов (AttributeSet), а третий еще дополнительно принимает стиль «по умолчанию». В большинстве случаев вам достаточно реализовать второй конструктор, который показан выше.
Теперь мы создали view и чтобы его использовать, мы добавим его в файл разметки:
Обратите внимание, вам необходимо полностью указывать имя вашего класса, который вы реализовали.
В данный момент этот класс практически идентичен стандартному TextView, поэтому давайте добавим ему некоторую функциональность.
Добавление логики отображения
Поскольку этот view будет отображать длительность, давайте добавим метод setDuration().
Что делает этот метод? Он получает значение длительности, переводит его в текстовую строку в определенном формате, используя некую логику, а затем выводит с помощью метода setText(). В данном случае логика заключается в переводе секунд в минуты и дальнейшем разделении на часы и минуты. Также в методе присутствует логика для корректного отображения одной минуты: «1 minute» вместо «1 minute(s)».
В конце метода отформатированное время преобразуется в строку с помощью шаблона.
private static final String TEMPLATE = «Duration: %s«;
Шаблон начинается со слова «Duration», а далее жирными буквами отображается отформатированная строка.
Для простоты я использовал в коде строковые литералы. В нормальном коде, эти строковые литералы лучше перенести в strings.xml, это позволяет в дальнейшем локализовать их.
Использование полученного view
Попробуем использовать наш view. В xml файл разметки добавим 5 custom View.
А в activity в методе onCreate() после метода setContentView() добавим следующие строки.
Соберем проект и загрузим его в эмулятор. Полученный результат можно видеть на изображении ниже.
Добавление xml атрибутов
Если бы мы могли задавать шаблон, то созданный нами компонент был бы более универсальным. По идее можно добавить метод, который позволит нам устанавливать шаблон, однако это не так просто. Если подумать, длительности нам нужно устанавливать динамически, а шаблон скорее всего будет все время одинаковым. Поэтому вместо метода, давайте добавим нашему custom view новые xml атрибуты.
В первую очередь нужно создать в папке values файл attrs.xml, где мы сможем определить атрибуты. Для этого view мы добавим один атрибут — template типа string. Выглядеть это будет так:
Мы записали тег declare-styleable и задали имя TemplateTextView. Имя может быть произвольным, но обычно задают имя, совпадающее с именем view. В данном случае я не задаю имя DurationTextView, потому что планирую использовать template атрибут еще в другом view.
Далее мы определили новый атрибут, установив ему имя template и формат string. Помимо string существует множество других форматов, например, Color, Integer, Boolean, Reference и так далее.
Использовать новый атрибут в layout xml можно так.
Обратите внимание, в корневом элементе мы добавляем такую строчку.
xmlns:app= «http://schemas.android.com/apk/res-auto»
Это позволяет нам использовать атрибуты, которые определены в файле attrs.xml, в нашем layout. В данном случае мы задали префикс «app», хотя он может быть произвольным.
Для того чтобы использовать новый атрибут, нужно получить его значение в нашем custom view. Сначала заменяем приватную статическую константу «TEMPLATE» переменной «template». Затем добавляем в наш конструктор следующий код.
В первой строке мы получаем набор атрибутов, который содержит все атрибуты, существующие в xml файле. Метод obtainStyledAttributes() делает две основные вещи. Во-первых, он фильтрует все атрибуты из attrs с помощью контекста, применяет стили и resolves reference to values. Во-вторых, он возвращает только те атрибуты, которые вы определили. Они задаются вторым аргументом, который представляет собой массив ссылок на требуемые атрибуты. В данном случае в R.styleable.TemplateTextView у нас только один атрибут R.attr.template, который мы создали в файле attrs.xml.
Далее мы используем массив attributes, чтобы получить значение атрибута. Если значение шаблона не было определено или не содержит «%s», мы устанавливаем template = «%s». И в заключении нужно не забыть освободить ресурсы с помощью функции recycle().
После некоторых изменений в layout, приложение будет выглядеть так:
Здесь я установил шаблон для первых трех custom view
Большая часть view в Android имеет методы, которые позволяют устанавливать значения XML атрибутов в коде. Возможно вам тоже понадобиться добавить какой-нибудь метод, это зависит от того, как вы будете использовать view.
В следующей части мы посмотрим как нарисовать свой собственное содержимое view компонента и сделаем view для отображения графика.
Исходники к статье можно скачать c GitHub.
По материалам сайта Jayway
Вольный перевод — Pavel Bobkov.
Источник
Android курс в Технополисе 2019
В этом уроке мы научимся создавать собственные View .
Custom View
Обычно термин Custom View обозначает View , которого нет в sdk Android. Или другими словами — это View которое мы сделали сами.
Когда может понадобиться реализация собственного View :
- специфичная отрисовка;
- специфичная обработка жестов;
- оптимизация существующих элементов;
- правка багов в существующем элементе.
Как правило, создание custom view можно избежать используя темы, различные параметры View , а иногда и лисенеры. Но, если все таки вам действительно нужно сделать что-то особенное, давайте разберемся как же это сделать.
Для начала, давайте вспомним о том, как выглядит иерархия базовых компонентов:
Все ui компоненты наследуются от View , а лейауты от ViewGroup . В свою очередь ViewGroup наследуется от View .
Прежде чем наследоваться от базового класса View посмотрите, может быть вам ближе функциональность уже какого-то существующего элемента. Например Button , это не написанный с нуля компонент, а наследник TextView .
Жизненный цикл View
Первостепенно давайте разберемся с жизненным циклом View .
Constructor
Каждый элемент начинает свое существование с конструктора. У View их целых четыре штуки:
Создание View из кода:
Создание View из XML:
Создание View из XML со стилем из темы:
Создание View из XML со стилем из темы и/или с ресурсом стиля:
Последний конструктор добавлен в sdk версии 21. Каждый из конструктор каскадно вызывает следующий.
onAttachedToWindow
После того как родитель View вызовет метод addView(View) , наш View будет прикреплён к окну. На этой стадии наш View-компонент попадает в иерархию родителя.
onMeasure
Этот метод означает, что наш View находится на стадии определения собственного размера. Для того что бы понять как распределить элементы на экране и сколько они занимают место нужно получить от каждого View его размер. В методе measure как раз и происходят расчеты.
Давайте посмотрим на сам метод:
Метод onMeasure() принимает 2 аргумента: widthMeasureSpec и heightMeasureSpec . Это значения, которые содержат в себе информацию о том, каким размером хочет видеть ваше View родительский элемент.
Каждое из значений на самом деле содержит 2 параметра:
- mode . Указывает на то, какие правила применяются ко второму параметру size;
- size . Непосредственно размер View .
Получить эти параметры можно при помощи методов класса MeasureSpec :
mode может принимать следующие значения:
- MeasureSpec.EXACTLY . Означает, что размер задан жёстко. Независимо от размера вашего View , вы должны установить определённую ширину и высоту;
- MeasureSpec.AT_MOST . Означает что View может быть любого размера, которого пожелает, но, не больше чем размер родителя. Это значение match_parent ;
- MeasureSpec.UNSPECIFIED . Означение что View может само решить какой размер ему нужен не взирая ни на какие ограничения. Это значение wrap_content .
В коде это можно описать следующим образом:
где wrapWidth , это наша желаемая ширина. Аналогичный подход применяется и к высоте View .
Конечно же не нужно каждый раз писать эту конструкцию из условий. Для упрощения работы у View есть метод
который уже включает в себя все необходимые условия.
После того как мы выполнили все расчеты, необходимо установить рассчитанные размеры при помощи метода:
Расчет размера можно разделить на 4 стадии:
- Родитель узнает “пожелания”, каким размером View хочет быть, определение LayoutParams наследника. Это может быть сделано как через xml, так и кодом:
- Родитель начинает измерять свои дочерние View и просит рассчитать их размеры.
- Дочерняя View рассчитывает свои размеры и устанавливает значение.
- Родитель сообщает о том, что расчет закончен и можно получить финальные значения.
onLayout
Этот метод позволяет присваивать позицию и размер дочерним элементам ViewGroup . В случае, если мы наследовались от View , нам не нужно переопределять этот метод.
onDraw
Это основной метод при разработки собственной View . В onDraw вы можете рисовать все что вам нужно. Метод имеет следующую сигнатуру:
На полученном Canvas вам требуется непосредственно изобразить саму View . Рисование на Canvas происходит при мощи объекта Paint . Paint отвечает за то, как именно будет отрисован контент вашего View и имеет множество параметров.
Стоит обратить внимание, что onDraw вызывается не один раз и может занимать много времени. Поэтому стоит максимально аккуратно работать с отрисовкой, не аллоцировать никаких объектов и не делать лишних операций.
Обновление View
Из диаграммы жизненного цикла видно, что существуют два метода, которые заставляют View перерисовываться:
invalidate() . Используется когда нужно только перерисовать ваш элемент. Когда изменился цвет или текст или нужно сделать какие-то еще визуальные изменения;
requestLayout() . Используется когда нужно изменить размеры вашего View . Вызов requestLayout не только заставит View заново измериться, но и перерисует элемент.
Иерархия
Вызовы всех методов View проходят от базового View к потомкам, сверху вниз.
Во время расчета размера View потомок принимает “пожелания” от родителя, рассчитывает свои размеры, а также размеры своих потомков. (Measure pass)
После того как размеры известны, родитель проставляет размеры и расположение своим потомкам. (Layout pass)
Последним этапом является отрисовка. Она также происходит от родителя к потомку
Источник
Реализация Custom View-компонента в Android
Каждый день мы пользуемся разными приложениями и несмотря на их различные цели, большинство из них очень похожи между собой с точки зрения дизайна. Исходя из этого, многие заказчики просят специфичные, индивидуальные макеты внешний вид, который не воплотило ещё ни одно приложение, чтобы сделать своё Android приложение уникальным и отличающимся от других.
Если какая-то специфичная особенность, из тех которые просит заказчик, требует особые функциональные возможности, которые невозможно сделать с помощью встроенных в Android View-компонентов, тогда нужно реализовывать собственный View-компонент (Custom View). Это не значит, что нужно всё взять и бросить, просто потребуется некоторое время на его реализацию, к тому же это довольно интересный и увлекательный процесс.
Я недавно попал в похожую ситуацию: мне нужно было создать индикатор страниц для Android ViewPager. В отличи от iOS, Android не предоставляет такой View-компонент, поэтому мне пришлось делать его самому.
Я потратил довольно много времени на его реализацию. К счастью, этот Custom View-компонент можно использовать и в других проектах, поэтому чтобы сэкономить личное время и время других разработчиков, я решил оформить всё это дело в виде библиотеки. Если вам нужен похожий функционал и не хватает времени на его реализацию собственными силами, можете взять его с этого GitHub репозитория.
Рисуем!
Так как в большинстве случаев разработка Custom View-комопонента занимает больше времени, чем работа с обычными View-компонентами, создавать их целесообразно только тогда, когда нет более простого способа реализовать специфичную особенность, или когда у вас есть ниже перечисленные проблемы, которые Custom View-компонент может решить:
- Производительность. Если у вас есть много View-компонентов в одном layout-файле и вы хотите оптимизировать это с помощью создания единственного Custom View-компонента;
- Большая иерархия View-компонентов, которая сложна в эксплуатации и поддержке;
- Полностью настраиваемый View-компонент, которому нужна ручная отрисовка;
Если вы ещё не пробовали разрабатывать Custom View, то эта статья — отличная возможность окунуться в эту тему. Здесь будет показана общая структура View-компонента, как реализовывать специфичные вещи, как избежать распространённые ошибки и даже как анимировать ваш View-компонент!
Первая вещь, которую нам нужно сделать, это погрузиться в жизненный цикл View. По какой-то причине Google не предоставляет официальную диаграмму жизненного цикла View-компонента, это довольно распространённое среди разработчиков непонимание, поэтому давайте рассмотрим её.
Constructor
Каждый View-компонент начинается с Constructor’а. И это даёт нам отличную возможность подготовить его, делая различные вычисления, устанавливая значения по умолчанию, ну или вообще всё что нам нужно.
Но для того чтобы сделать наш View-компонент простым в использовании и установке, существует полезный интерфейс AttributeSet. Его довольно просто реализовать и определённо стоит потратить на это время, потому что он поможет вам (и вашей команде) в настройке вашего View-компонента с помощью некоторых статических параметров на последующих экранах. Во-первых, создайте новый файл и назовите его «attrs.xml». Этот файл может содержать все атрибуты для различных Custom View-компонентов. Как вы можете видеть в этом примере есть View-компонент названный PageIndicatorView и один атрибут piv_count.
Во-вторых, в конструкторе вашего View-компонента, вам нужно получить атрибуты и использовать их как показано ниже.
- При создании кастомных атрибутов, добавьте простой префикс к их имени, чтобы избежать конфликтов имён с другими View-компонентами. Обычно добавляют аббревиатуру от названия View-компонента, поэтому у нас префикс «piv_»;
- Если вы используете Android Studio, то Lint будет советовать вам использовать метод recycle() до тех пор пока вы сделаете это с вашими атрибутами. Причина заключается в том, что вы можете избавиться от неэффективно связанных данных, которые не будут использоваться снова;
onAttachedToWindow
После того как родительский View-компонент вызовет метод addView(View), этот View-компонент будет прикреплён к окну. На этой стадии наш View-компонент будет знать о других View-компонентах, которые его окружают. Если ваш View-компонент работает с View-компонентами пользователя, расположенными в том же самом «layout.xml» файле, то это хорошее место найти их по идентификатору (который вы можете установить с помощью атрибутов) и сохранить их в качестве глобальной ссылки (если нужно).
onMeasure
Этот метод означает, что наш Custom View-компонент находится на стадии определения собственного размера. Это очень важный метод, так как в большинстве случаев вам нужно определить специфичный размер для вашего View-компонента, чтобы поместиться на вашем макете.
При переопределении этого метода, всё что вам нужно сделать, это установить setMeasuredDimension(int width, int height).
При настройке размера Custom View-компонента вы должны обработать случай, когда у View-компонента может быть определённый размер, который пользователь (прим. переводчика: программист работающий с вашим View-компонентом) будет устанавливать в файле layout.xml или программно. Для вычисления этого свойства, нужно проделать несколько шагов:
- Рассчитать размер необходимый для содержимого вашего View-компонента (ширину и высоту);
- Получить MeasureSpec вашего View-компонента (ширину и высоту) для размера и режима;
- Проверить MeasureSpec режим, который пользователь устанавливает и регулирует (для ширины и высоты);
Посмотрите на значения MeasureSpec:
- MeasureSpec.EXACTLY означает, что пользователь жёстко задал значения размера, независимо от размера вашего View-компонента, вы должны установить определённую ширину и высоту;
- MeasureSpec.AT_MOST используется для создания вашего View-компонента в соответствии с размером родителя, поэтому он может быть настолько большим, насколько это возможно;
- MeasureSpec.UNSPECIFIED — на самом деле размер обёртки View-компонента. Таким образом, с этим параметром вы можете использовать желаемый размер, который вы расчитали выше.
Перед установкой окончательных значений в setMeasuredDimension, на всякий случай проверьте эти значения на отрицательность Это позволит избежать любых проблем в предпросмотре макета.
onLayout
Этот метод позволяет присваивать размер и позицию дочерним View-компонентам. У нас нет дочерних View-компонентов, поэтому нет смысла переопределять этот метод.
onDraw
Вот здесь происходит магия. Два объекта, Canvas и Paint, позволяют вам нарисовать всё что вам нужно. Экземпляр объекта Canvas приходит в качестве параметра для метода onDraw, и по существу отвечает за рисование различных фигур, в то время как объект Paint отвечает за цвет этой фигуры. Простыми словами, Canvas отвечает за рисование объекта, а Paint за его стилизацию. И используется он в основном везде, где будет линия, круг или прямоугольник.
Создавая Custom View-компонент, всегда учитывайте, что вызов onDraw занимает довольно много времени. При каких-то изменениях, сроллинге, свайпе вы будете перерисовывать. Поэтому Andorid Studio рекомендует избегать выделение объекта во время выполнения onDraw, вместо этого создайте его один раз и используйте в дальнейшем.
- При отрисовке, имейте в виду переиспользование объектов вместо создания новых. Не полагайтесь на вашу IDE, которая должна подсветить потенциальную проблему, а сделайте это самостоятельно, потому что IDE может не увидеть этого, если вы создаёте объекты внутри методов вызываемых в onDraw;
- Во время отрисовки, не задавайте размер прямо в коде. Обрабатывайте случай, когда у других разработчиков может быть тот же самый View-компонент, но с другим размером, поэтому делайте ваш View-компонент зависимым от того размера, который ему присвоен;
View Update
Из диаграммы жизненного цикла View-компонента, вы можете заметить что существует два метода, которые заставляют View-компонент перерисовываться. Методы invalidate() и requestLayout() могут помочь вам сделать ваш Custom View-компонент интерактивным, что собственно поможет изменять его внешний вид во время выполнения. Но почему их два?
Метод invalidate() используется когда просто нужно перерисовать View-компонент. Например, когда ваш View-компонент обновляет свой текст, цвет или обрабатывает прикосновение. Это значит, что View-компонент будет вызывать только метод onDraw, чтобы обновить своё состояние.
Метод requestLayout(), как вы можете заметить будет производить обновление View-компонента через его жизненный цикл, только из метода onMeasure(). А это означает, что сразу после обновления View-компонента вам нужно его измерить, чтобы отрисовать его в соответствии с новыми размерами.
Animation
Анимации в Custom View-компонентах, это по кадровый процесс. Это означает, что если вы например захотите сделать анимированным процесс изменения радиуса круга от маленького к большому, то вам нужно увеличивать его последовательно и после каждого шага вызывать метод invalidate для отрисовки.
Ваш лучший друг в анимации Custom View-компонентов — это ValueAnimator. Этот класс будет помогать вам анимировать любые значения от начала до конца и даже обеспечит поддержку Interpolator (если нужно).
Не забывайте вызывать метод Invalidate каждый раз, когда изменяется значение анимации.
Надеюсь, что эта статья поможет вам сделать свой первый Custom View-компонент. Если вы захотите получить больше информации по этой теме, то может посмотреть хорошее видео.
Источник