了解FrameLayout的关键概念——Android SDK中最简单的ViewGroup,通常用于放置一个子元素或叠加多个元素。FrameLayout适用于占位容器、片段、加载指示器和前景元素。它不管理子元素的布局——每个后续元素都绘制在前一个元素之上,它们的位置由layout_gravity(left、top、right、bottom、center)决定。基本场景在FrameLayout API参考中有描述。
要点总结
FrameLayout是Android SDK中最简单的ViewGroup,设计用于阻挡屏幕区域并显示一个子视图(或叠加多个)。与LinearLayout(顺序排列)和RelativeLayout(相对定位)不同,FrameLayout不改变子元素的位置——默认情况下每个新的child都放置在左上角(0,0)并绘制在前一个之上。
FrameLayout从API Level 1就已存在,至今仍是Android最轻量的容器:它不用复杂逻辑重写onMeasure,执行最少的布局操作。根据Android Performance Blog,FrameLayout只需一次传递即可执行onLayout,与直接放置View相比几乎不增加额外开销。这使其成为速度至关重要的容器的理想选择:RecyclerView项布局(配合ConstraintLayout进行定位)、Fragment容器、叠加层。
FrameLayout的默认大小由最大的子元素决定(如果未设置match_parent)。如果未指定任何child,FrameLayout会收缩到(0,0)。android:measureAllChildren属性(见下文)会改变此行为。
FrameLayout继承自ViewGroup,是许多专用容器的直接父类:FragmentContainerView(片段)、CardView(带阴影的卡片)、ScrollView(带单个child)、NestedScrollView。在创建带有Fragment导航的Activity时,Android Studio的标准模板使用FrameLayout(或FragmentContainerView)作为片段的主容器。
android:foreground——FrameLayout属性,指定在所有子元素之上绘制的drawable。与background(在子元素下方)不同,foreground显示在内容之上并且可以透明。用于:按下时的叠加效果(通过?attr/selectableItemBackground实现涟漪效果)、图像遮罩、显示状态指示器(图像上方的选中标记)。
Foreground支持标准drawable资源:ColorDrawable、ShapeDrawable、RippleDrawable、LayerDrawable。从API 23+开始,android:foregroundGravity可用于前景定位(fill、center、top、bottom)。在“填充”(fill)模式下,前景拉伸到整个FrameLayout;在center模式下——绘制在中心。
android:measureAllChildren——布尔属性(默认为true),决定在计算FrameLayout大小时是否需要测量所有子元素。如果为true(默认),FrameLayout会考虑所有子元素的尺寸,包括GONE(尺寸为0)。如果为false,FrameLayout只测量VISIBLE和INVISIBLE子元素——GONE元素被排除在计算之外。根据Google I/O 2019,对于包含大量GONE元素的容器(例如具有可见性切换的列表),禁用measureAllChildren可使初始渲染加快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和带有layout_gravity="bottom|end"的TextView,用于在上方定位。
<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)。带有layout_gravity="bottom|end"的TextView放置在图像上方的右下角。elevation=2dp在标签下方添加阴影,从视觉上将其与图像分开。这是一个最小的示例,在LinearLayout中需要嵌套容器或自定义代码。
带有内容和居中进度条的屏幕,进度条在加载时出现。FrameLayout包含两个元素:内容和visibility="gone"的ProgressBar(在代码中切换)。
<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的标准容器。Activity根据导航替换此容器内的片段。
<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()完全替换内容,不影响Activity。
FrameLayout在四种场景下最优:片段容器(FragmentContainerView或FrameLayout)、加载叠加层(内容上方的ProgressBar)、卡片标签(图像上的标记)、ViewStub的占位容器(懒加载)。
不适用于:多个元素的复杂定位(使用ConstraintLayout)、顺序列表(LinearLayout或RecyclerView)、改变元素顺序的动态布局。
FrameLayout作为屏幕的根元素——如果屏幕包含超过2–3个元素,则是不良做法。对于根容器,请使用ConstraintLayout——它在不损失性能的情况下提供对定位的更多控制。仅在有叠加或占位需要的子区块中使用FrameLayout。
常见问题解答
FrameLayout——没有定位系统(只有layout_gravity)的简约容器,用于元素叠加。ConstraintLayout——具有约束、链、屏障、百分比尺寸和MotionLayout支持的完整布局系统。FrameLayout在简单情况下(1个child或叠加层)更快;ConstraintLayout对于复杂布局是必需的。选择:FrameLayout用于加载叠加层和片段容器;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轴顺序。
FrameLayout更快,用于极其简单的情况——一个子元素,无需复杂定位。FrameLayout在最短时间内执行onMeasure,不计算约束。然而,差异仅在数百次重复时显著(RecyclerView中数万个元素)。对于普通屏幕(1–3个FrameLayout),差异在微秒级别,不影响用户体验。ConstraintLayout是95%任务的通用选择。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。