- Kotlin Android Extensions: полное руководство
- Что такое Kotlin Android Extensions?
- Как этим пользоваться
- Включаем Kotlin Android Extensions в наш код
- Восстанавливаем view из XML
- Магия Kotlin Android Extensions
- Kotlin Android Extensions ― работа с фрагментами
- Kotlin Android extensions и Custom View
- Восстанавливаем views из другого view
- Kotlin Android Extensions версии 1.1.4
- Применяем на ViewHolder (или любом пользовательском классе)
- Kotlin Android Extension. Реализация Parcelable
- Настройка кэша
- Заключение
- How to Create Your Own Custom View in Android with Kotlin?
- What is a Custom View in Android?
- Why do we even need them in our apps?
- How are we going to create our own Custom View?
- 1. onMeasure() Method
- Exploring Kotlin initialization with Android custom views
- A closer look at the relationship between Kotlin and programmatic/dynamic view inflation
- When are Kotlin constructors and init blocks called?
- What happens when a view is created?
- Case 1: Custom Kotlin View inflated via XML
- Case 2: Custom Kotlin View inflated programmatically
- The Recommendation
Kotlin Android Extensions: полное руководство
Oct 9, 2018 · 7 min read
Если у вас уже есть опыт разработки приложений для Android, вероятно, вы уже устали использовать findViewById, чтобы восстанавливать view. Или, может быть, вы уже сдались и начали использовать известную библиотеку ― Butterknife. Если это ваш случай, то вы влюбитесь в Kotlin Android Extensions.
Что такое Kotlin Android Extensions?
Kotlin Android Extensions — это плагин для Kotlin, который включён в стандартный пакет. Он позволяет восстанавливать view из Activities, Fragments, и Views удивительно-бесшовным способом.
Плагин генерирует дополнительный код, который позволяет получить доступ к view в виде XML, так же, как если бы вы имели дело с properties с именем id, который вы использовали при определении структуры.
Т а кже он создаёт локальный кэш view. При первом использовании свойства, плагин выполнит стандартный findViewById. В последующем, view будет восстановлен из кэша, поэтому доступ к нему будет быстрее.
Как этим пользоваться
Посмотрим, насколько это просто. В первом примере рассмотрим activity:
Включаем Kotlin Android Extensions в наш код
Хотя плагин уже интегрирован (устанавливать новый не нужно), если вы хотите его использовать, вам нужно подключить плагин в модуль Android:
И это все что вам потребуется. Теперь вы можете начать работу с ним.
Восстанавливаем view из XML
С этого момента, восстановить view, также просто, как использовать view id, которое вы определили в XML, непосредственно в вашем activity.
Представьте, что у вас есть такой XML:
Как видно, в TextView есть id welcomeMessage .
Просто напишите следующий код в MainActivity:
Чтобы это работало, нужен специальный импорт (я написал его ниже), но IDE может автоматически импортировать его. Проще и быть не может!
Как я уже упоминал, сгенерированный код включает кэш view, поэтому, если вы повторно обратитесь к view, это не потребует выполнения findViewById снова.
Давайте посмотрим, что происходит под капотом.
Магия Kotlin Android Extensions
Когда начинаешь работать с Kotlin, действительно интересно понять байт-код, который генерируется при использовании той или иной функции. Это поможет вам понять скрытую цену ваших решений.
В меню Tools → Kotlin, вы найдёте мощный инструмент Show Kotlin Bytecode. Если кликнуть по нему, вы увидите байт-код, который будет сгенерирован, когда открытый файл класса будет скомпилирован.
Для большинства людей, байт-код не особенно полезен, но тут есть ещё одна опция: Decompile.
Эта опция покажет вам Java репрезентацию байт-кода, который был сгенерирован в Kotlin. Так, вы сможете более-менее понять, как будет выглядеть код Java, эквивалентный тому, что вы написали на Kotlin.
Я собираюсь использовать это на примере activity и посмотреть код, сгенерированный Kotlin Android Extensions.
Вот интересная часть:
Это и есть кэш view, о котором мы говорили.
При запросе view, его поиск начинается в кэше. Если view нет в кэше, то он туда будет добавлен, как только будет найден. Всё просто.
Кроме того, в код добавляется функция для очистки кэша: clearFindViewByIdCache. Её можно использовать, скажем, когда старые view уже не актуальны и вам нужно получить новые.
Тогда, эта строка:
Превращается в эту:
Таким образом, плагин не генерирует свойства для каждого view, это ненастоящие свойства. Плагин просто заменяет код во время компиляции, чтобы получить доступ к кэшу view. Затем, он приводит к нужному типу и вызывает метод.
Kotlin Android Extensions ― работа с фрагментами
Этот плагин можно использовать с фрагментами.
Проблема с фрагментами заключается в том, что view может обновится, а экземпляр фрагмента останется прежним. Что произойдёт? А то, что view находящееся в кэше, перестанет быть валидным.
Давайте посмотрим код, который генерирует плагин, если мы используем его с фрагментом. Я создаю простой фрагмент, который использует тот же XML, который я написал выше:
В onViewCreated , я снова изменяю текст TextView . Что насчёт сгенерированного байт-кода?
Всё тоже самое, как и в activity, с небольшой разницей:
Когда view перестанет быть актуальным, этот метод вызовет clearFindViewByIdCache , так что мы в безопасности!
Kotlin Android extensions и Custom View
С custom view, плагин работает схожим образом. Скажем, у нас есть такой view:
Я создаю очень простое custom view и генерирую конструкторы с новым намерением, которое использует аннотацию @JvmOverloads :
В примере выше, я изменяю текст в itemTitle . Сгенерированный код должен искать view в кэше. Не имеет смысла снова копировать тот же код полностью. Вы можете наблюдать это в строке, которая изменяет текст:
Отлично! В custom views мы тоже вызываем findViewById только первый раз.
Восстанавливаем views из другого view
Последняя альтернатива, которую предлагает Kotlin Android Extensions: использовать свойства напрямую из другого view.
Я использую схему, схожую с той, что была в предыдущем разделе. Представьте, что это было закачано в адаптер для примера.
Вы также можете получить доступ к subviews напрямую, просто используя этот плагин:
Хотя плагин и может помочь заполнить import, здесь есть небольшое отличие:
Несколько вещей, которые вам нужно знать:
- Во время компиляции, вы можете ссылаться на любое view из любого другого view. Это значит, вы можете ссылаться на view, которое не является его прямым потомком. Это плохо скажется на времени выполнения при попытке восстановить view, которого не существует.
- В этом случае, view не кэшировано, как в случае с Activities и Fragments.
Почему так? В отличие от предыдущих случаев, здесь у плагина нет места, чтобы генерировать необходимый код для кэша.
Если вы снова перечитаете код, который сгенерирован плагином, где вызываются свойства из view, вы увидите это:
Как видно, здесь нет запроса к кэшу. Будьте осторожны, если у вас комплексное view, и вы используете его в адаптере. Это может повлиять на производительность.
Также у вас есть альтернатива: Kotlin 1.1.4
Kotlin Android Extensions версии 1.1.4
Начиная с этой, новой версии Kotlin, в Android Extensions включили новые интересные функции: кэш в любом классе (включая ViewHolder ) и новая аннотация, названная @Parcelize . Также есть способ настраивать сгенерированный кэш.
Мы вернёмся к ним через минуту, но вам следует знать, что эти новые фичи ― не завершены, так что, вам нужно включить их с помощью build.gradle :
Применяем на ViewHolder (или любом пользовательском классе)
Теперь есть простой способ построить кэш для любого класса. Единственное требование заключается в том, чтобы класс имплементировал интерфейс LayoutContainer . Этот интерфейс предоставит view, которое плагин будет использовать, чтобы искать subviews. Представьте, что у вас есть ViewHolder, который содержит view со структурой, описанной в предыдущем примере. Всё что вам нужно сделать:
containerView ― единственное, что мы перезаписываем из интерфейса LayoutContainer . Это все что вам необходимо.
С этого момента, у вас есть доступ к views напрямую. Нет необходимости присоединять itemView, чтобы получить доступ к subviews.
Ещё раз, если вы посмотрите на сгенерированный код, вы увидите, что view берётся из кэша:
Здесь я применял его на ViewHolder , но это достаточно универсальный подход и его можно использовать с любым классом.
Kotlin Android Extension. Реализация Parcelable
С новой аннотацией @Parcelize , можно с лёгкостью имплементировать Parcelable c любым классом.
Вам нужно добавить аннотацию, а плагин сделает за вас всю сложную работу:
Далее, как вам должно быть известно, можно добавить объект к любому намерению:
И восстановить объект из намерения в любой точке (в этом случае: из activity):
Настройка кэша
В этот экспериментальный набор, включена новая фича ― аннотация @ContainerOptions . Она даёт возможность настраивать способ построения кэша, или даже предотвращать его создание классом.
По умолчанию, используется Hashmap , как мы видели ранее. Его можно заменить на SparseArray из Android framework, что может быть более эффективно в некоторых ситуациях. Или, если вам не нужен кэш для класса, по каким-то причинам, такая возможность тоже есть.
Вот как это использовать:
На данный момент существуют следующие варианты:
Заключение
Вы увидели, как легко работать с Android views в Kotlin. Благодаря простому плагину, можно забыть обо всём этом ужасном коде, связанном с восстановлением view. Этот плагин создаёт для нас необходимые свойства с приведением правильного типа, безо всяких проблем.
Кроме того, Kotlin 1.1.4 добавляет интересные фичи, которые будут действительно полезны в некоторых случаях, к которым плагин ранее не имел отношения.
Источник
How to Create Your Own Custom View in Android with Kotlin?
In this article, we’re going to talk about how we can create our own custom view in Android step by step. We all know that in the beginning android platform provides us some basic views for example – TextView, ImageView, EditText, Button, ImageButton, RadioButton, etc. But, sometimes we need a whole new type of view where our prebuilt layouts and widgets fail to meet our needs. So, let’s get dive into it.
What is a Custom View in Android?
First, the concept of Custom View in Android is nothing but having our own customized view model other than the prebuilt ones. And how do we achieve that? By simply bringing together different types of prebuilt views and then use the combined one as a single view.
Why do we even need them in our apps?
We all know that in the beginning android platform provides us some basic views for example – TextView, ImageView, EditText, Button, ImageButton, RadioButton, etc. But, sometimes we need a more interactive and complex type of view where our prebuilt layouts and widgets fail to meet our needs. There the custom views come into the picture. Now the custom views in Android are fully customized by the developers in order to achieve the goal. Although custom views aren’t needed everywhere. But, at a higher development level where complexity is more, we need them.
How are we going to create our own Custom View?
Whenever we’re going to create a custom view for our apps, we need some items under consideration. Introduce yourself to the lifecycle of a view in Android.
Now, 3 basic methods are to be taken under importance.
- onMeasure()
- onLayout()
- onDraw()
These 3 methods will be overridden in the corresponding Java/Kotlin class.
1. onMeasure() Method
As suggested by the method name it is used for measurement purposes. Basically, we can control the width and height of our custom view.
- If not overridden: The size of the view will be either ‘match_parent’ or ‘wrap_content’.
- If overridden: On overriding this method we have more control of size over the custom view. Do not call the method ‘super.onMeasure()’. Instead, we’ll be calling the method ‘setMeasuredDimension(width, height)’.
Overriding onMeasure():
When this method is called we get ‘widthMeasureSpec’&‘heightMeasureSpec’as parameters. A size mode is a constraint that is set by the parent for its child’s views. Available 3 types of modes are listed below.
- UNSPECIFIED: No constraints are given, so it can be of whatever size it wants.
- EXACTLY: Exact size is determined for the child views.
- AT_MOST: Child can be as large as it wants up to the specified size.
Источник
Exploring Kotlin initialization with Android custom views
A closer look at the relationship between Kotlin and programmatic/dynamic view inflation
Today, we explore the question “Where does initialization occur around the lifecycle of a view?” This is topic my teammates hotly debated over for months, so I’ve decided to see for myself. This blurb covers two ways of inflating a custom view for comparison: via layout resource and programmatically.
For this exploration, we look at a small compass application. For convenience, you can download the code on Github to follow along/run the app on your own device.
This app uses View Binding and a custom view to spin the needle based on where the user’s device is facing. The two different cases exist on different branches from the main branch.
When are Kotlin constructors and init blocks called?
Like Java, Kotlin can declare multiple constructors, but makes a differentiation between primary and secondary constructors. Both are denoted with the keyword constructor .
In most cases, a Kotlin class will only use a primary constructor. If there are no visibility modifiers or annotations, then a class can omit the constructor keyword. A Kotlin class without a primary constructor exists will generate one that does nothing.
A primary constructor may also include an initializer block denoted with the keyword init . The initializer block executes the logic as soon as the class is initialized as part of the primary constructor.
Secondary constructors are mostly used for Java interoperability. For the case of CompassView below, no primary constructor is declared, but multiple secondary constructors are:
These secondary constructors will delegate to the proper constructor in the super class, or find one that does. But in what order, specifically, would a secondary constructor execute in relation to the primary constructor?
From Kotlin official documentation:
Delegation to the primary constructor happens as the first statement of a secondary constructor, so the code in all initializer blocks and property initializers is executed before the secondary constructor body.
In CompassView , the initialization block executes before the secondary constructors do. But where does Kotlin class initialization fall in relation to the View lifecycle, exactly? We answer this by examining how a view is inflated.
What happens when a view is created?
The answer depends on how a view is added in a tree. On the screen, all views in Android exists in a single tree. You can add views to that tree two ways:
- Programmatically: adding a View to that tree.
- XML: specifying another tree to via an Android layout file.
In our compass application, we inflate our custom CompassView on to the MainActivity as the only custom view component. This article demonstrates the differences between inflating a custom view both via XML ( CompassView(context, attrs) ) and code( CompassView(context) ).
Case 1: Custom Kotlin View inflated via XML
When you put a view element into an XML file, Android will use a LayoutInflater to parse and map the corresponding objects in the XML to the inflated Views. LayoutInflater retrieves the resource by opening up ResourceManager and examining all the possible layouts it could match within its current configuration. Android will then parse back the appropriate binary XML resource.
For CompassView(context, attrs) the second argument will utilize the properties necessary to tell the view hierarchy how to size and place the element in the tree. This post does not focus on these phases (Measure/Layout/Draw) of the View lifecycle, but there is a totally awesome talk from Drawn out: How Android renders (Google I/O ’18) that dives deep down into the mechanisms for whomever is curious.
This process of inflation is then repeated for its children and its children’s children, recursively until all views have been inflated. When inflation is complete, LayoutInflater sends a callback onFinishInflate() from the children views back up to the root to indicate the view is ready to interact with.
Now that we’ve described the process a bit more, let’s examine the code needed to instantiate our custom CompassView with XML:
In MainActivity , the CompassView via the view binding reference from the XML. When initializing the custom view this way, the default secondary constructor is executed:
The Kotlin initialization block is called first, then the secondary constructor CompassView(context, attrs) . Remember, there is no primary constructor for extending a View class (unless you create one yourself), so it makes sense that init is executed first before the secondary constructor does. Because calling CompassView(context, attrs) uses LayoutInflater , the onFinishInflate() callback is made when all views have finished inflating.
Case 2: Custom Kotlin View inflated programmatically
For the most part, initializing views via XML is the preferred way of creating view elements in Android since it becomes part of the tree view hierarchy. There are advantages to this: using XML is much more friendly for Android memory since it can compress easily, and helps to take off runtime workload a programmatic draw might have to do.
Suppose you have a case where you cannot include the creation of a custom view in the XML, but rather, you must initialize the view only at runtime.
This is what the code might look like:
As you can see, there’s a significant work load setting up the UI programmatically just to make the CompassView element fit in with ConstraintLayout. This particular case would be incredibly impractical in real life, but there is a special reason we talk about initializing CompassView(this) . To demonstrate, we’ll run this code:
Like the previous case, the Kotlin initialization block executes first, then the secondary constructor CompassView(context) . However, you might have noticed that no onFinishInflate() callback is ever made. This is because CompassView(context) is not instantiated by XML, meaning that no LayoutInflater is put into play. There’s no children to wait on for recursive instantiation, and so there’s no onFinishInflate() for callback.
If you needed to add shared logic for different constructor calls that doesn’t depend on this system call, this might pose a problem for creating more manual view bindings in older code and other UI-related actions.
The Recommendation
You can annotate a custom view with the @JvmOverloads annotation, which tells the Kotlin compiler to generate overloaded methods. Every overload will substitute with default values for any parameters omitted in the generated methods, which means that the code below is equivalent to the CompassView class constructors written in the beginning of this article:
Instead of depending on the system to call onFinishInflate , you can use Kotlin init < >for shared logic (even with views dynamically inflated in the code). Running the inflate method in the initialization block will guarantee inflation occurs no matter how you choose to initialize your custom view.
I hope you enjoyed this blurb: in Android, looking at certain concepts through the lens of Kotlin can shed new perspective to seemingly basic questions. You can find the recommended version of the code on the main project, and additional resources below.
Источник