Много было сказано про «красоту» кода на Java, но на мой взгляд, главное — не инструмент, а умение им пользоваться. Под катом попытка написать декларативный DSL для вёрстки под Android даже не изобретая новый язык программирования!
Вёрстка на Java всегда ассоциировалась у меня с болью.
float dp = getResources().getDisplayMetrics().density;
FrameLayout root = new FrameLayout(this);
root.setBackgroundColor(RED);
root.setLayoutParams(
new ViewGroup.LayoutParams(
MATCH_PARENT,
(int)(100f*dp)));
FrameLayout child = new FrameLayout(this);
child.setBackgroundColor(GREEN);
FrameLayout.LayoutParams childParams =
new FrameLayout.LayoutParams(
(int)(50f*dp),
(int)(50f*dp));
childParams.gravity = CENTER;
child.setLayoutParams(childParams);
root.addView(child);
Результат:
И дело даже не в том, что код выглядит страшно (а он страшный как чёрт). Основная проблема в том, что в нём невозможно не ошибиться. Я 3 раза перезаливал сюда этот код, в первый и второй разы наивно полагая, что смогу всё правильно написать сразу, и тщательно перепроверив всё лишь в третий. Скажете, что дело в моей невнимательности и будете правы, но если даже в такой простой вёрстке можно накосячить, то что уж говорить про что-то более сложное?
Но почему с вёрсткой на Java всё так грустно? На мой взгляд основная причина — возможность верстать в xml и отсутствие инструмента для вёрстки на Java.
Минусы xml
Для меня их 3.
Первый — оверхед.
Зачем тратить ресурсы и без того не очень мощных устройств на Android на такие операции, как inflate и findViewById? На оптимизацию этих операций было потрачено много времени и сил, но они от этого не стали бесплатными.
Второй — громоздкость.
<FrameLayout
android:background="#f00"
android:layout_width="match_parent"
android:layout_height="100dp">
<FrameLayout
android:background="#0f0"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"/>
</FrameLayout>
Удручает необходимость дублировать тэги, писать перед каждым атрибутом «android:», а после вырабатывать частиную слепоту чтобы читать этот код.
Третий — ограниченность языка.
Допустим, я хочу сделать подпись к автару больше самого аватара на 10dp.
<ImageView
android:layout_width="@dimen/avatarSide"
android:layout_height="@dimen/avatarSide"/>
<TextView
android:layout_width="@dimen/avatarSide + 10dp"
android:layout_height="wrap_content"/>
Но этого сделать нельзя потому что xml не поддерживает выражения.
Почему не Anko?
Anko — это DSL, с помощью которого можно декларативно описывать разметку на Kotlin.
frameLayout {
backgroundColor = RED
frameLayout {
backgroundColor = GREEN
}.lparams(dip(50), dip(50)) {
gravity = CENTER
}
}.lparams(matchParent, dip(100))
Получаем все возможности полноценного языка программирования, лучшую производительность и даже не мучаемся с вёрсткой интерфейса на Java!
Всё прекрасно, но, на мой взгляд, неприлично тянуть за собой целый рантайм языка при разработке библиотек. 500 кб — не так много для конечного приложения, но для библиотеки — явно перебор.
JAnko
Как оказалось, возможностей Java хватает чтобы верстать декларативно.
new frameLayout(this) {{
new lparams(this) {{
width = MATCH_PARENT;
height = dip(100);
}}._();
backgroundColor = RED;
new frameLayout(this) {{
new lparams(this) {{
width = dip(50);
height = dip(50);
gravity = CENTER;
}}._();
backgroundColor = GREEN;
}}._();
}}._();
Язык поддерживает блоки кода без названия. Они выполняются перед конструктором класса сразу после конструктора класса-родителя.
class A {
// block
{
// some code
}
}
Именно эту возможность Java я использовал чтобы не писать название переменной, в которой лежит виджет для изменения каждого его свойства.
Пример с аватаркой и подписью.
new imageView(this) {{
new lparams(this) {{
width = dimen(R.dimen.avatarSide);
height = dimen(R.dimen.avatarSide);
}}._();
}}._();
new textView(this) {{
new lparams(this) {{
width = dimen(R.dimen.avatarSide) + dip(10);
height = WRAP_CONTENT;
}}._();
}}._();
Выглядит немного странно.
Похоже на человека в монокле и оператор на scala. Но для proof of concept — вполне достаточно.
Итоги
0). На Kotlin код выглядит вот так:
object : frameLayout(this) {
init {
object : lparams(this) {
init {
width = MATCH_PARENT
height = dip(100f)
}
}.`_`()
backgroundColor = RED
object : frameLayout(this) {
init {
object : lparams(this) {
init {
width = dip(50f)
height = dip(50f)
gravity = CENTER
}
}.`_`()
backgroundColor = GREEN
}
}.`_`()
}
}.`_`()
1) Вес aar составляет 12кб
2) Idea не сбивает форматирование
3) Коду на Java иногда можно придать неожиданный для Java вид
Репозиторий с библиотекой и примерами
Бенчмарк
Обычно чуть быстрее Anko, что забавно.
Я ожидал, что моя мини-библиотека станет последним пристанищем этого монстра, но даже оттуда её выпилил в пользу Litho, который выполняет measurment и layout в другом потоке. Спасибо eirnym за ссылку.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Закопать обратно?
64.94% Да263
35.06% Нет142
Проголосовали 405 пользователей. Воздержались 174 пользователя.