Android custom view size

Введение

Это будет серия постов на тему создания пользовательского 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 стадии:

  1. Родитель узнает “пожелания”, каким размером View хочет быть, определение LayoutParams наследника. Это может быть сделано как через xml, так и кодом:

  1. Родитель начинает измерять свои дочерние View и просит рассчитать их размеры.

  1. Дочерняя View рассчитывает свои размеры и устанавливает значение.

  1. Родитель сообщает о том, что расчет закончен и можно получить финальные значения.
Читайте также:  Как удалить точки доступа андроид

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)

Последним этапом является отрисовка. Она также происходит от родителя к потомку

Источник

Creating custom and compound views in Android — Tutorial

This tutorials describes how to create custom and combound views with Android.

1. Custom Views

1.1. Default views

The Android framework provides several default views. The base class a view is the View . Views are responsible for measuring, layouting and drawing themselves and their child elements (in case of a ViewGroup ). Views are also responsible for saving their UI state and handling touch events. Developers can also create custom views and use them in their application.

It is possible to create custom views by:

Compound views — combining views with a default wiring

Custom views — creating your own views

by extending an existing view, e.g. Button

by extending the View class

The following image shows the default view hierarchy of Android.

View are typically created to provide a user interface experience with is not possible with the default views. Using custom view allows the developer allow to do certain performance optimization, i.e., in case of a custom layout the development can optimize the layout manager for his use case.

1.2. How Android draws the view hierarchy

Once an activity receives the focus, it must provide the root node of its layout hierarchy to the Android system. Afterwards the Android system starts the drawing procedure.

Drawing begins with the root node of the layout. The layout hierarchy is traversed in the order of declaration, i.e., parents are drawn before their children and children are drawn in the order of declaration.

Drawing the layout is a two pass process:

measuring pass — implemented in the`measure(int, int)` method. This happens as a top-down traversal of the view hierarchy. Every view stores its measurements.

layout pass — implemented in the layout(int, int, int, int) method. This is also a top-down traversal of the view hierarchy. During this phase each layout manager is responsible for positioning all of its children. It uses the sizes computed in the measure pass.

The measure and layout step always happen together.

Layout managers can run the measure pass several times. For example, LinearLayout supports the weight attribute which distributes the remaining empty space among views and RelativeLayout measures child views several times to solve constraints given in the layout file.

A view or activity can trigger the measure and layout pass with a call to the requestLayout() method.

After the measure and layout calculation, the views draw themselves. This operation can be triggered with the invalidate() method from the View class.

For a detailed introduction into the deeper layer of Android see http://source.android.com/devices/graphics/architecture.html.

1.3. Using new views in layout files

Custom and compound views can be used in layout files. For this you need to use the full qualified name in the layout file, e.g. using the package and class name.

Alternatively you can also declare you name space in the layout file, similar to the Android name space.

1.4. Create screenshots of views

Every View class support the creating of an image of its current display. The following coding shows an example for that.

2. Compound Views

Compound views (also known as Compound Components ) are pre-configured ViewGroups based on existing views with some predefined view interaction.

Combound views also allow you to add custom API to update and query the state of the combound view.

Читайте также:  Java android работа с камерой

For such a control you define a layout file and assign it to your compound view. In the implementation of your compound view you predefine the view interaction. You would define a layout file and extend the corresponding ViewGroup class. In this class you inflate the layout file and implement the View connection logic

For performance reasons you may want to rewrite your combound view to a custom view which extends View . This may you can typically flatten your view hierarchy. Drawing the view requires in this case less traversals and this can be significantly faster if implemented correctly.

3. Creating custom views

3.1. Creating custom views

By extending the View class or one of its subclasses you can create your custom view.

For drawing view use the onDraw() method. In this method you receive a Canvas object which allows you to perform drawing operations on it, e.g. draw lines, circle, text or bitmaps. If the view should be re-drawn you call the invalidate() method which triggers a call to the onDraw() method of this view.

If you define own views, ensure you review the ViewConfiguration class, as it contains several constants for defining views.

To draw your Views you typically use the 2D Canvas API.

3.2. Measurement

The layout manager calls the onMeasure() method of the view. The view receives the layout parameter from the layout manager. A layout manager is responsible to determine the size of all its children.

The view must call the setMeasuredDimenstion(int, int) method with the result.

3.3. Defining custom layout managers

You can implement your custom layout manager by extending the ViewGroup class. This allows you to implement more efficient layout managers or to implement effects which are currently missing in the Android platform.

A custom layout manager can override the onMeasure() and onLayout() method and specialize the calculation of its children. For example it can leave out the time consuming support of layout_weight of the LinearLayout class.

To calculate the size of the child you can use the measureChildWithMargins() method of the ViewGroup class.

It is good practice to store any additional layout parameters in an inner class of your ViewGroup implementation. For example ViewGroup.LayoutParams ` implements command layout parameters, and `LinearLayout.LayoutParams implements additional parameters specific to LinearLayout, as for example the layout_weight parameter.

4. Life cycle of a Andoid view

A view is displayed if it is attached to a layout hierarchy which is attached to a window. A view has several life cycle hooks.

The onAttachedToWindow() is called once the window is available.

The onDetachedFromWindow() is used when the view is removed from its parent (and if the parent is attached to a window). This happens for example if the activity is recycled (e.g. via the finished() method call) or if the view is recycled.

The onDetachedFromWindow() method can be used to stop animations and to clean up resources used by the view.

4.2. Traversal life cycle events

Traversals life cycle events consists of Animate, Measure, Layout and Draw.

All views must know how to measure and layout themselves. The requestLayout() method call tells the view to measure and layout itself. As this operation may influence the layout of other views it calls also requestLayout() of its parent.

This recursive call is the reason why you should not nestle layout to deeply. The measure and layout operation might be expensive if a lot of hierarchies are re-calculated.

The onMeasure() method determines the size for the view and its children. It must set the dimension via the setMeasuredDimension() method in this method call before returning.

The onLayout() positions the views based on the result of the onMeasure() method call. This call happens typically once, while onMeasure() can happen more than once.

4.3. Activity life cycle

Views don’t have access to the life cycle events of the activities. If views want to get informed about these events, you should create an interface in the view which you call in the life cycle methods of the activity.

5. Define additional attributes for your custom Views

You can define additional attributes for your compound or custom views. To define additional attributes create an attrs.xml file in your res/values folder. The following shows an example of attributes defined for a new view called ColorOptionsView .

To use these attributes in your layout file you have to declare them in the XML header. In the following listing this is done via the xmlns:custom part of the code. These attributes are also assigned to the view.

The following example shows how you components can access these attributes.

Источник

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