- Рисование собственных представлений (View) в Android
- Получите полный контроль над представлением и оптимизируйте его производительность
- Введение
- Общий подход
- Обновление представления
- invalidate()
- requestLayout()
- Creating custom and compound views in Android — Tutorial
- 1. Custom Views
- 1.1. Default views
- 1.2. How Android draws the view hierarchy
- 1.3. Using new views in layout files
- 1.4. Create screenshots of views
- 2. Compound Views
- 3. Creating custom views
- 3.1. Creating custom views
- 3.2. Measurement
- 3.3. Defining custom layout managers
- 4. Life cycle of a Andoid view
- 4.1. Life cycle events related to the window
- 4.2. Traversal life cycle events
- 4.3. Activity life cycle
- 5. Define additional attributes for your custom Views
Рисование собственных представлений (View) в Android
Получите полный контроль над представлением и оптимизируйте его производительность
В преддверии старта курса «Android Developer. Professional» приглашаем всех желающих принять участие в открытом вебинаре на тему «Пишем gradle plugin».
А пока делимся переводом полезного материала.
Введение
Разработчики постоянно проектируют различные виды пользовательских интерфейсов с помощью XML, но в дополнение к этому можно довольно легко освоить создание собственных представлений, которые открывают новые преимущества и позволяют избежать повторного использования шаблонного кода.
В Android доступен широкий набор готовых виджетов и макетов для создания пользовательского интерфейса, однако они не могут удовлетворить все требования наших приложений. И здесь на помощь приходит возможность создания собственных представлений. Создав собственный подкласс представления, можно получить максимально полный контроль за внешним видом и функционалом экранного элемента.
Прежде чем приниматься за работу с собственными представлениями, полезно изучить жизненный цикл представления.
Зачем создавать собственные представления?
Чтобы реализовать собственное представление, в большинстве случаев понадобится больше времени, чем если использовать обычные представления. Создавать собственные представления стоит лишь в том случае, если нет другого, более простого способа реализовать нужную вам возможность или если у вас есть какие-либо из указанных ниже проблем, которые можно устранить за счет создания собственного представления.
Производительность: в вашем макете много представлений и вы хотите оптимизировать их, нарисовав одно, более легкое собственное представление.
Имеется сложная иерархия представлений, которую трудно использовать и поддерживать.
Необходимо создать специализированное представление, требующее рисования вручную.
Общий подход
Чтобы приступить к созданию компонентов для реализации собственных представлений, необходимо выполнить следующие основные шаги.
Создать класс, расширяющий базовый класс или подкласс представления.
Реализовать конструкторы, использующие атрибуты из XML-файла.
Переопределить некоторые методы родительского класса (onDraw(), onMeasure() и т. д.) в соответствии с нашими требованиями.
После выполнения этих шагов созданный расширяющий класс можно использовать вместо представления, на основе которого он был создан.
Пример
В одном из моих проектов мне нужно было создать круглый виджет TextView для отображения количества уведомлений. Чтобы достичь этой цели, нужно создать подкласс TextView.
Шаг 1. Создадим класс с именем CircularTextView .
Шаг 2. Расширим класс виджета TextView. Здесь под TextView в IDE выдается ошибка, в которой сообщается, что у этого типа есть конструктор и он должен быть инициализирован.
Шаг 3. Добавим конструкторы в класс.
Это можно сделать двумя способами.
Первый способ добавления конструкторов в класс показан ниже.
Другой способ заключается в добавлении аннотации @JvmOverloads к вызову конструктора, как показано ниже.
Часто нас сбивает с толку то, что у представления есть несколько разных типов конструкторов.
View(Context context)
Простой конструктор для динамического создания представления из программного кода. Здесь параметр context — это контекст, в котором работает представление и через который можно получить доступ к текущей теме, ресурсам и т. д.
View(Context context, @Nullable AttributeSet attrs)
Конструктор, который вызывается при формировании представления из XML-файла. Он вызывается, когда представление создается из XML-файла, содержащего атрибуты представления. В этом варианте конструктора используется стиль по умолчанию (0), поэтому применяются только те значения атрибутов, которые есть в теме контекста и заданном наборе AttributeSet .
Шаг 4. Самый важный шаг в отрисовке собственного представления — это переопределение метода onDraw() и реализация необходимой логики отрисовки внутри этого метода.
Метод OnDraw (canvas: Canvas?) имеет параметр Canvas (холст), с помощью которого компонент представления может отрисовывать себя. Для рисования на холсте необходимо создать объект Paint.
Как правило, процесс рисования определяется двумя аспектами:
что рисовать (определяется объектом Canvas);
как рисовать (определяется объектом Paint).
Например, Canvas предоставляет метод для рисования линии, а Paint предоставляет методы для определения цвета этой линии. В нашем случае объект Canvas в классе CircularTextView предоставляет метод для рисования окружности, а объект Paint заполняет ее цветом. Проще говоря, Canvas определяет, какие фигуры можно нарисовать на экране, а Paint определяет свойства нарисованных фигур — цвет, стиль, шрифт и т. д.
Давайте займемся кодом. Мы создаем объект Paint и присваиваем ему некоторые свойства, а затем рисуем фигуру на холсте (объект Canvas), используя наш объект Paint. Метод onDraw() будет выглядеть так:
IDE показывает предупреждение о том, что следует избегать выделения объектов во время операций отрисовки или операций с макетом. Это предупреждение возникает потому, что метод onDraw() много раз вызывается при отрисовке представления, в котором каждый раз создаются ненужные объекты. Поэтому, чтобы избежать ненужного создания объектов, мы вынесем соответствующую часть кода за пределы метода onDraw() , как показано ниже.
При выполнении отрисовки всегда помните о том, что следует повторно использовать объекты вместо создания новых. Ваша IDE может указать на потенциальные проблемы, но полагаться на нее не стоит. Например, она не сможет отследить случай, когда объекты создаются внутри методов, вызываемых из метода onDraw() . Поэтому лучше проверять все самостоятельно.
Шаг 5. Мы закончили с рисованием. Теперь давайте внесем этот класс представления в XML.
Добавьте этот XML-макет в вашу активность (Activity) и запустите приложение. Вот что будет на экране.
Выглядит неплохо, правда? Теперь сделаем так, чтобы значение динамическому свойству цвета в circlePaint назначалось из активности, а также добавим контур к кружку. Для этого в классе CircularTextView необходимо создать несколько методов-сеттеров, чтобы можно было вызывать эти методы и устанавливать свойства динамически.
Для начала давайте реализуем настройку цвета отрисовки. Для этого создадим сеттер, как показано ниже.
Теперь мы можем устанавливать цвет из нашей активности динамически, вызывая этот метод.
Неплохо, правда? Теперь давайте добавим контур к кружку. Контур будет задаваться двумя входными параметрами: шириной линии контура и ее цветом. Чтобы задать цвет линии контура, нам нужно создать объект Paint точно так же, как мы это делали для кружка. Чтобы задать ширину линии контура, мы создадим переменную, установим для нее нужное значение и используем его в методе onDraw() . Полный код будет выглядеть так:
Теперь в активности можно динамически настраивать эти атрибуты нужным образом.
Далее давайте запустим приложение, устанавливая различные цвета для нашего виджета.
Итак, теперь стало ясно, как динамически устанавливать свойства из активности, но возникает вопрос о том, как устанавливать атрибуты из XML. Продолжим наше исследование.
Для начала создадим файл с именем attrs.xml в папке values. Этот файл будет содержать все атрибуты для различных представлений, которые мы создаем сами. В приведенном ниже примере у нашего представления под названием CircularTextView имеется атрибут ct_circle_fill_color , который принимает значение цвета. Аналогичным образом мы можем добавить и другие атрибуты.
Затем нам нужно будет прочитать эти свойства в классе, который мы создали для реализации собственного представления. В блоке инициализации мы считываем набор атрибутов, как показано ниже.
Теперь просто переходим к XML-макету и устанавливаем значение свойства, соответствующее нужному цвету, после чего запускаем приложение. На выходе мы увидим нужный результат.
В моем случае результат был таким:
Примечание. При рисовании не задавайте жестко размер вашего представления, так как им могут воспользоваться другие разработчики с применением других размеров. Рисуйте представление в соответствии с его текущим размером.
Обновление представления
Итак, мы задали собственное представление. Если мы хотим обновлять представление при изменении какого-нибудь свойства или по какой-то другой причине, этого можно добиться двумя основными способами.
invalidate()
invalidate() — это метод, который инициирует принудительную перерисовку определенного представления. Проще говоря, метод invalidate() следует вызывать в случае, когда требуется изменение внешнего вида представления.
requestLayout()
Если в какой-то момент происходит изменение состояния представления, то метод requestLayout() сообщает системе представлений, что необходимо сделать перерасчет фаз «измерение» (Measure) и «макет» (Layout) для данного представления (измерение → макет → рисование). Проще говоря, метод requestLayout() следует вызывать в случае, когда требуется изменение границ представления.
Теперь, я надеюсь, вы знаете в общих чертах, как создавать собственные представления. Чтобы они демонстрировали отличную производительность, необходимо освоить все описанные здесь методы.
Источник
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.
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
4.1. Life cycle events related to the window
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.
Источник