Научете ключовите понятия за FrameLayout — най-простата ViewGroup в Android SDK, предназначена за поставяне на един дъщерен елемент (обикновено) или наслагване на няколко елемента един върху друг. FrameLayout е удобен за placeholder контейнери, фрагменти, индикатори за зареждане и елементи на преден план. Той не управлява разположението на децата — всеки следващ елемент се рисува върху предишния, а позицията им се определя от layout_gravity (left, top, right, bottom, center). Основните сценарии са описани в FrameLayout API Reference.
Основни точки
FrameLayout е най-простата ViewGroup в Android SDK, проектирана за блокиране на област от екрана и показване на един дъщерен изглед (или наслагване на няколко). За разлика от LinearLayout (последователно поставяне) и RelativeLayout (относително позициониране), FrameLayout не променя позицията на децата — всяко ново child по подразбиране се поставя в горния ляв ъгъл (0,0) и се рисува върху предишното.
FrameLayout се появи в API Level 1 и остава най-лекият Android контейнер: той не презаписва onMeasure със сложна логика и извършва минимален брой layout операции. Според Android Performance Blog, FrameLayout изпълнява onLayout за едно преминаване и практически не добавя допълнителни разходи в сравнение с директното поставяне на View. Това го прави идеален избор за контейнери, където скоростта е важна: layout-и на елементи в RecyclerView (в комбинация с ConstraintLayout за позициониране), контейнери за фрагменти, overlay слоеве.
Размерът по подразбиране на FrameLayout се определя от най-големия дъщерен елемент (ако не е зададен match_parent). Ако не е посочен никакъв child, FrameLayout се свива до (0,0). Атрибутът android:measureAllChildren (раздел по-долу) променя това поведение.
FrameLayout наследява ViewGroup и е пряк родител на много специализирани контейнери: FragmentContainerView (фрагменти), CardView (карти със сянка), ScrollView (с едно child), NestedScrollView. При създаване на Activity с Fragment навигация, стандартният шаблон на Android Studio използва FrameLayout (или FragmentContainerView) като основен контейнер за фрагменти.
android:foreground — атрибут на FrameLayout, който задава drawable, рисуван върху всички дъщерни елементи. За разлика от background (под децата), foreground се показва над съдържанието и може да бъде прозрачен. Използва се за: overlay ефекти при натискане (ripple чрез ?attr/selectableItemBackground), маскиране на изображения, показване на индикатори за състояние (отметка за избор върху изображение).
Foreground поддържа стандартни drawable ресурси: ColorDrawable, ShapeDrawable, RippleDrawable, LayerDrawable. От API 23+ е достъпен android:foregroundGravity за позициониране на foreground (fill, center, top, bottom). В режим "запълване" (fill) foreground се разтяга върху целия FrameLayout; в режим center — рисува се в центъра.
android:measureAllChildren — булев атрибут (по подразбиране true), който определя дали всички дъщерни елементи трябва да бъдат измервани при изчисляване на размера на FrameLayout. Ако е true (по подразбиране), FrameLayout взема предвид размерите на всички деца, включително GONE (с размер 0). Ако е false, FrameLayout измерва само VISIBLE и INVISIBLE деца — GONE елементите се изключват от изчислението. Според Google I/O 2019, изключването на measureAllChildren за контейнери с много GONE елементи (например списъци с превключване на видимост) ускорява първоначалното рендиране с 20–60%.
android:layout_gravity — атрибут на дъщерния елемент на FrameLayout (и други ViewGroup), който определя неговата позиция вътре в контейнера. В FrameLayout layout_gravity е единственият начин за управление на позицията на дъщерния елемент, тъй като FrameLayout не предоставя собствени правила за позициониране (като RelativeLayout) или посока (като LinearLayout).
Възможни стойности: top, bottom, left, right, center, center_horizontal, center_vertical, fill, fill_horizontal, fill_vertical, clip_horizontal, clip_vertical. Комбинират се чрез |: android:layout_gravity="bottom|center_horizontal" — елементът е притиснат до долния ръб и центриран хоризонтално. За елементи, чийто размер е по-малък от FrameLayout, layout_gravity определя позицията им в свободното пространство.
Ако layout_gravity не е зададен, по подразбиране елементът се поставя в горния ляв ъгъл (top|left). За FrameLayout, съдържащ няколко деца, всяко може да има свой собствен layout_gravity — един елемент може да е в горния ляв ъгъл, друг в долния десен, трети в центъра. Това позволява създаването на прости наслагвания (например кръстче за затваряне върху изображение).
android:gravity (родителски атрибут) подравнява съдържанието вътре в FrameLayout — например текст вътре в TextView. android:layout_gravity (дъщерен атрибут) подравнява самия елемент вътре в FrameLayout. В контекста на FrameLayout, gravity определя как децата се разполагат по подразбиране (аналог на layout_gravity за всички деца наведнъж), но layout_gravity на всеки конкретен елемент презаписва родителската стойност.
Изображение и текстов етикет в долния десен ъгъл. FrameLayout съдържа ImageView на цял екран и TextView с layout_gravity="bottom|end" за позициониране отгоре.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/product_photo"
android:scaleType="centerCrop" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:background="@drawable/badge_background"
android:elevation="2dp"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:text="-30%"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
</FrameLayout>
ImageView заема целия FrameLayout (200dp височина). TextView с layout_gravity="bottom|end" се поставя в долния десен ъгъл върху изображението. elevation=2dp добавя сянка под етикета, визуално го отделяйки от изображението. Това е минимален пример, който в LinearLayout би изисквал вложен контейнер или персонализиран код.
Екран със съдържание и прогрес бар в центъра, който се появява при зареждане. FrameLayout съдържа два елемента: съдържанието и ProgressBar с visibility="gone" (превключва се в кода).
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/content_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Заредено съдържание"
android:textSize="18sp" />
<ProgressBar
android:id="@+id/loading_spinner"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
ProgressBar е скрит по подразбиране (gone). При започване на зареждане, в кода се извиква findViewById(R.id.loading_spinner).visibility = View.VISIBLE — спинерът се появява в центъра върху съдържанието. След зареждане — .visibility = View.GONE. FrameLayout осигурява наслагване без промяна на позицията на съдържанието — текстът не се измества при появата на спинер, тъй като ProgressBar се рисува отгоре.
FrameLayout като стандартен контейнер за FragmentTransaction. Активността заменя фрагментите в този контейнер в зависимост от навигацията.
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
import androidx.fragment.app.FragmentTransaction
val fragmentContainer = R.id.fragment_container
fun navigateTo(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(fragmentContainer, fragment)
.addToBackStack(null)
.commit()
}
FrameLayout като контейнер за фрагменти — най-лекият начин за поддръжка на Fragment Navigation. FragmentContainerView (наследник на FrameLayout) се препоръчва с Navigation Component 2.4+, но простият FrameLayout остава валиден за ръчни FragmentTransaction. Ключовото предимство — FragmentTransaction.replace() напълно заменя съдържанието, без да засяга активността.
FrameLayout е оптимален за четири сценария: контейнер за фрагменти (FragmentContainerView или FrameLayout), overlay за зареждане (ProgressBar върху съдържание), етикети на карти (знак върху изображение), placeholder контейнер за ViewStub (мързеливо зареждане).
Не е подходящ за: сложно позициониране на множество елементи (използвайте ConstraintLayout), последователни списъци (LinearLayout или RecyclerView), динамични оформления с промяна на реда на елементите.
FrameLayout като коренен елемент на екрана — лоша практика, ако екранът съдържа повече от 2–3 елемента. За коренен контейнер използвайте ConstraintLayout — дава повече контрол върху позиционирането без загуба на производителност. Използвайте FrameLayout само за дъщерни блокове, където е необходимо наслагване или placeholder.
Често задавани въпроси
FrameLayout — минималистичен контейнер без система за позициониране (само layout_gravity), предназначен за наслагване на елементи. ConstraintLayout — пълноценна система за оформление с ограничения, вериги, бариери, процентни размери и поддръжка на MotionLayout. FrameLayout е по-бърз за прости случаи (1 child или overlay); ConstraintLayout е задължителен за сложни оформления. Избор: FrameLayout за overlay за зареждане и контейнер за фрагменти; ConstraintLayout за всичко останало.
Задайте размер на FrameLayout wrap_content по желаната ос (android:layout_width="wrap_content" и/или android:layout_height="wrap_content"). В този случай FrameLayout ще се адаптира към размера на най-големия дъщерен елемент. Ако всички деца са по-малки от FrameLayout, излишното пространство ще остане празно. За точен контрол използвайте match_parent с фиксиран padding.
Това е нормалното поведение на FrameLayout — той не разпределя децата в пространството, а ги рисува последователно едно върху друго. Ако искате елементите да не се припокриват, използвайте друг контейнер (LinearLayout, ConstraintLayout). За частично припокриване с контрол на реда на рисуване използвайте layout_gravity за изместване и elevation за Z-order.
FrameLayout е по-бърз за максимално прости случаи — един дъщерен елемент, без сложно позициониране. FrameLayout изпълнява onMeasure за минимално време, без да изчислява ограничения. Разликата обаче е забележима само при стотици повторения (RecyclerView с десетки хиляди елементи). За обикновен екран (1–3 FrameLayout) разликата е в микросекунди и не влияе на UX. ConstraintLayout е универсалният избор за 95% от задачите.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също