Обясняваме основите на LinearLayout — основна ViewGroup в Android SDK за подравняване на дъщерни елементи в една линия: вертикално (LinearLayout.VERTICAL) или хоризонтално (LinearLayout.HORIZONTAL). LinearLayout остава най-простият и най-бързият начин за изграждане на ред от бутони, списък с настройки или формуляр за въвеждане, когато сложността на оформлението не надвишава 5–10 елемента. Атрибутите layout_weight и orientation управляват разпределението на пространството и посоката. Подробна спецификация — в LinearLayout API Reference.
Основни точки
LinearLayout — една от първите ViewGroup в Android SDK (API Level 1), предназначена за последователно поставяне на дъщерни елементи в един ред или колона. Всеки нов елемент се поставя след предишния, а посоката се задава от атрибута android:orientation. LinearLayout е най-простият и предвидим Android контейнер, което го прави идеален избор за линейни интерфейси: редове от бутони, списъци с настройки с икона и текст, хоризонтални менюта.
Исторически, LinearLayout е първата ViewGroup, с която се запознава начинаещият Android разработчик. Неговото предимство е прозрачността: дъщерните елементи се поставят стриктно в реда на декларация, без скрити правила за позициониране. За разлика от RelativeLayout, където елемент може да бъде свързан с произволен съсед, или ConstraintLayout с неговата система от ограничения, LinearLayout работи на принципа „първи отляво/отгоре, втори след него, трети след втория“. Според Google I/O 2017, 78% от екраните в топ 1000 приложения на Google Play използват LinearLayout като основен или помощен контейнер.
Основният показател на LinearLayout — LinearLayout.MeasureSpec — система за управление на размера. При ориентация VERTICAL ширината на всеки елемент е равна на ширината на родителя (match_parent или фиксирана), а височината се определя от съдържанието или атрибута. За HORIZONTAL обратно: височината е равна на родителската, ширината — според съдържанието. Ако в HORIZONTAL ред общата ширина на елементите надвишава ширината на родителя, елементите могат да излязат извън екрана (ако не се използва weight).
LinearLayout предефинира onMeasure и onLayout от ViewGroup. При ориентация VERTICAL onLayout поставя елементите по оста Y: първи child — y=0, втори — y=prev.bottom + divider.hidden, и така нататък. При HORIZONTAL — по оста X. Divider (разделител) се задава чрез android:divider и android:showDividers (beginning, middle, end). Разделителят може да бъде цвят, drawable или персонализиран Shape ресурс.
Ориентация — ключовият атрибут на LinearLayout, който определя посоката на поставяне на дъщерни елементи. android:orientation="vertical" подрежда елементите отгоре надолу; android:orientation="horizontal" — отляво надясно (или отдясно наляво при RTL локализация). Ако ориентацията не е зададена, по подразбиране се използва HORIZONTAL (на по-стари версии) — но изричното посочване е задължително за предвидимо поведение.
android:gravity — подравняване на съдържанието вътре в LinearLayout. Приема комбинации: top, bottom, left, right, center, center_horizontal, center_vertical, fill, clip_vertical, clip_horizontal. Стойността gravity="center" за VERTICAL оформление ще постави всички елементи центрирано хоризонтално. android:layout_gravity — атрибут на дъщерния елемент, който го подравнява вътре в родителския LinearLayout. Пример: бутон с layout_gravity="right" в VERTICAL оформление ще бъде притиснат до десния ръб.
Разлика: gravity — свойство на родителя (как децата са разположени вътре в него), layout_gravity — свойство на детето (как се държи вътре в родителя). Грешката да се объркат тези атрибути е една от най-честите при начинаещите.
За HORIZONTAL оформления с текстови елементи (TextView, Button, EditText) е полезно android:baselineAligned="true" — подравнява всички текстове по базовата линия на шрифта, независимо от техния padding, size или height. Това прави реда с икона, заглавие и описание визуално спретнат: текстът не скача нагоре и надолу.
layout_weight — атрибут на дъщерния елемент на LinearLayout, който определя каква част от свободното пространство ще заеме. Работи само в посоката на ориентация (ширина за HORIZONTAL, височина за VERTICAL). Формула за изчисление: размер = собствен размер + (свободно пространство × (тегло на елемента / сума от всички тегла)). Свободно пространство = размер на родителя — сума от всички собствени размери на децата.
layout_weight е по-ефективен от фиксираните размери за адаптивни интерфейси. Например, ако левият панел трябва да заема 30% от екрана, а десният — 70%, задайте weight=3 и weight=7. За правилна работа обикновено размерът в посоката се задава на 0dp (android:layout_width="0dp" за HORIZONTAL), тогава собственият размер на елемента се счита за нула и целият размер се определя само от weight. Това е стандартната препоръка на Google (Android Developers Guide, 2024).
Важно: теглата се сумират за всички елементи в LinearLayout, включително тези без weight (тяхното тегло се счита за 0). Ако един елемент има weight=1, а друг weight=2, първият ще заеме 1/3 от свободното пространство, вторият — 2/3. weightSum (атрибут на родителя) позволява задаване на максимална сума на теглото — ако теглата на децата общо са по-малки от weightSum, разликата остава като празно пространство.
Формулярът за вход се състои от три елемента: поле за имейл (заема 2/4), поле за парола (1/4) и бутон (1/4) от височината на екрана. И трите елемента с weight работят само в ориентация VERTICAL по височина.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:hint="Email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:hint="Парола"
android:inputType="textPassword" />
<Button
android:id="@+id/login_button"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Вход" />
</LinearLayout>
Височина на всеки елемент = (parent_height — padding) × (weight / sumWeights). Имейлът ще получи 2/4 от височината, паролата и бутонът — по 1/4. Всички height=0dp, така че собственият размер не се взема предвид — работи само weight.
Панел с три бутона: икона за назад (wrap_content), заглавие (запълва останалото) и икона за меню (wrap_content). weight за заглавие = 1, бутони без weight.
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical">
<ImageButton
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_back" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Заглавие"
android:textSize="18sp" />
<ImageButton
android:id="@+id/btn_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_more_vert" />
</LinearLayout>
Бутоните за назад и меню заемат точно толкова, колкото е необходимо за иконата. Заглавието (TextView) с width=0dp и weight=1 се разтяга в цялото останало пространство между тях. gravity="center_vertical" подравнява всички елементи по вертикален център.
Карта на продукт: хоризонтален ред (изображение + вертикален блок с текст). Външен LinearLayout HORIZONTAL, вътрешен — VERTICAL за заглавие, описание и цена.
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/product_image"
android:scaleType="centerCrop" />
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Име на продукта"
android:textStyle="bold"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Кратко описание на продукта в два реда"
android:textSize="14sp"
android:textColor="@color/gray" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1 299 rub."
android:textSize="18sp"
android:textColor="@color/accent" />
</LinearLayout>
</LinearLayout>
Изображение отляво (80×80dp), отдясно — вертикален блок от три реда текст. Вътрешният LinearLayout има weight=1, така че заема цялото останало място след изображението. Това е класически модел на卡片 от списък, който се среща в хиляди приложения.
Основният недостатък на LinearLayout — необходимостта от вложени контейнери за сложни оформления. Ако трябва да поставите елементи в мрежа 2×2 или да направите ред с подравняване към два ръба, са необходими поне 2–3 нива на влагане. Всяко ниво LinearLayout добавя 2 преминавания onMeasure, което на слаби устройства води до забележими задръжки при изобразяване.
Според Android Performance Blog (Google, 2017), всяко допълнително ниво на влагане увеличава времето за оформление с 30–50 μs. За списък от 20 елемента с 3 нива на влагане, закъснението може да достигне 3 ms на елемент — това е забележим jank при превъртане. ConstraintLayout решава този проблем чрез плоска йерархия, заменяйки 3–4 вложени LinearLayout с един контейнер с ограничения.
Препоръка: използвайте LinearLayout за прости линейни оформления (до 10 елемента, едно ниво на влагане). За карти от списъци, формуляри с различно подравняване и екрани с таблична структура изберете ConstraintLayout. За повтарящи се елементи в RecyclerView използвайте ConstraintLayout във файла за оформление на елемента — това е от решаващо значение за производителността при превъртане.
Често задавани въпроси
Най-честата причина — размерът в посоката не е зададен на 0dp. За HORIZONTAL оформление е необходимо android:layout_width="0dp", за VERTICAL — android:layout_height="0dp". Ако елементът има wrap_content или match_parent, weight може да не работи или да даде неочакван резултат. Втора причина — weightSum на родителя е по-малко от сумата на weight на децата. Трета — използване на match_parent на дъщерен елемент в противоположната посока.
layout_weight се задава на всеки дъщерен елемент и определя неговия дял от свободното пространство. weightSum — атрибут на родителя LinearLayout, който задава максималната сума на теглата. По подразбиране weightSum = сумата от всички layout_weight на децата. Ако зададете weightSum = 3 и деца с weight=1, всяко ще заеме 1/3. Ако има само две деца с weight=1, останалата 1/3 ще бъде празна. weightSum е удобен за фиксиране на пропорции без преизчисляване при добавяне/премахване на елементи.
По подразбиране няма разстояния — елементите са поставени един до друг. Ако се появят разстояния, проверете: android:divider и android:showDividers на родителя, android:layout_margin на дъщерните елементи. Допълнително може да повлияе android:weightSum — ако елементите използват weight, между тях може да остане празно пространство поради неправилно изчисление. За нулеви разстояния задайте на всички деца margin=0dp и премахнете divider.
За прости еднопосочни оформления (1–5 елемента в ред/колона) LinearLayout е малко по-бърз — не харчи ресурси за изчисляване на ограничения. За сложни оформления (повече от 5 елемента, мрежи, различни видове подравняване) ConstraintLayout е по-бърз, тъй като използва плоска йерархия вместо вложени LinearLayout. Според тестовете на Google (Android Performance Blog), ConstraintLayout печели при оформления, изискващи 3+ нива на влагане на LinearLayout.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също