Pull to refresh

Рано закапывать Java

Reading time 3 min
Views 24K
image

Много было сказано про «красоту» кода на 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 за ссылку.
Only registered users can participate in poll. Log in, please.
Закопать обратно?
64.85% Да 262
35.15% Нет 142
404 users voted. 174 users abstained.
Tags:
Hubs:
+11
Comments 27
Comments Comments 27

Articles