前语
请说一下Android里边有哪几种布局?它们别离是哪些?
这样的问题大家必定在自己的面试阅历傍边被问到过,或许还不止一次,当然这样的问题也很简略,基本都是送分题,遇到了都挺快乐,都能答的上,可是自从2019年的谷歌IO大会亮相了Compose,人们榜首次认识到在Android里边也能够运用声明式ui的方法来开发咱们的页面,到最近几年,Compose技术日趋成熟化,甚至也支持跨平台技术,国内部分大厂也开端逐渐在项目傍边启用Compose,作为Android开发,咱们需要意识到,Compose马上就要从一个加分技术变成一个必备技术了,或许某一天,各个厂子招聘Android开发的时分,Compose也将成为一个重要目标,所以开头的那个问题有或许到了将来就会变成
请说一下Compose里边有哪几种布局?它们别离是哪些?
面试官假如遽然问出这样一个问题,你会不会将即将脱口而出的”Linear….”又吞了回去?又或许你说出来了几个,面试官就会先给你一个谜之微笑,然后围绕着你说出来的布局又提出了几个更深化的问题,直到你也给他一个谜之微笑。所以为了避免这一个为难的情况发生,咱们有必要去学习把握一些关于Compose方面的常识,而这篇文章就先从布局开端,一共涉及到八个论题,别离是:
- 问题一:Compose中都有哪些布局?
- 问题二:给Column设置了verticalArrangement或许horizontalAlignment特点,可是没有收效,或许是什么原因导致的?
- 问题三:假如在一个Row布局里边,父布局跟子视图一起设置了笔直方向的对齐方法,是会报错?仍是会以哪一个为准?
- 问题四:Arrangement都有哪几种对齐方法
- 问题五:为什么说Column是笔直线性布局,而Row是水平线性布局
- 问题六:Surface为什么不能像Box那样运用Modifier.background设置布景色彩?
- 问题七:除了运用翻滚组件,还有什么办法能够让一个页面上下滑动
- 问题八:什么是固有特性丈量?它是干什么用的?
问题一:Compose中都有哪些布局?
Compose里边的布局主要有
- Column:笔直方向线性布局
- Row:水平方向线性布局
- Box:帧布局
- ConstraintLayout:束缚布局
- Scaffold:脚手架布局
问题二:给Column设置了verticalArrangement或许horizontalAlignment特点,可是没有收效,或许是什么原因导致的?
Column
是Compose里边的笔直布局,相当于纵向方向的LinearLayout
,运用它能够完成子视图笔直方向线性布局,咱们这儿写个比方
咱们看到这儿完成了两个案牍笔直方向的布局,方位在左侧靠上方位,这个设置咱们能够从Column
的结构函数中一眼就能发现,咱们看下
咱们看到verticalArrangement
参数是纵向布局,默许是Arrangement.Top
靠上方位,另一个horizontalAlignment
参数表明横向布局,默许是Alignment.Start
靠左方位,那已然是表明方位的参数,那么咱们在刚刚的比方里边也加一下,让它能够笔直居中,代码如下
能够看到虽然加了笔直方向的参数,可是如作用图所示并没有将视图笔直居中显现,那笔直的不可咱们试试水平的呢?
的确是水平居中的了,可是跟咱们想象中的不太一样,是第二个文本相关于榜首个文本水平居中,也便是说Column
里边的方向参数是针关于它的子视图来排版的,相当于咱们LinearLayout
的android:gravity
特点,咱们也能够从Column的参数阐明中知道这一点
注释上也说了,这俩参数是作用于layout的children上的,所以咱们刚刚设置笔直参数的时分看似没有作用,其完成已收效了,只是Column
的巨细是自适应于两个Text的巨细,所以没有看出来,咱们能够给Column
设置个高度,就变得明显了
所以假如你给Column
或许Row
设置了verticalArrangement
或许horizontalAlignment
参数后没有收效,能够看下是不是忘掉设置宽高了
问题三:假如在一个Row布局里边,父布局跟子视图一起设置了笔直方向的对齐方法,是会报错?仍是会以哪一个为准?
咱们这有段示例代码,有一个Row
的布局,在里边放了两个按钮,在Row
里边设置的是笔直居中与水平居中,现在假如我给AA这个Button也设置个对齐方法,比方靠下对齐,那么按钮AA的真实情况会怎样样呢?咱们改下代码
运行后的成果如下所示
所以这个问题的总结是:假如当父布局与它的子视图一起设置了对齐方法,那么会优先依照子视图的对齐方法来,子视图的对齐方法优先级较高。
问题四:Arrangement都有哪几种对齐方法
Arrangement.Start
全体水平靠左
Arrangement.Center
全体居中
Arrangement.End
全体水平靠右
Arrangement.SpaceBetween
两边各放置一个,剩下的均匀分布在剩下空间
Arrangement.SpaceArround
一切视图左右两边空出相等距离
Arrangement.SpaceEvenly
一切视图均匀分布一切空间
问题五:为什么说Column是笔直线性布局,而Row是水平线性布局
面试的时分大家都会有这么一个感觉,平常越是简略寻常的东西,越会拿出来问,导致一些平常不简单去重视的东西,遽然被问到的时分,就一脸黑人问号?就比方咱们常用的Column
和Row
,大家都了解一个是笔直线性布局,一个是水平线性布局,可是凭什么Column
是笔直的而不是线性的,Row
里边的子视图又为什么不能竖着排,这些大家在平常的开发过程中有没有想过呢?咱们就从剖析它们的源码的角度看看,这两种布局是怎样管理自己的子视图的,首要榜首步咱们都会从两者的结构函数追寻进去,会发现Column
与Row
都会先去生成自己的丈量战略MeasurePolicy
,然后再把丈量战略作为一个参数传递给Layout
那什么是丈量战略呢?假如说Layout
是真实去做丈量,布局子视图作业的中心部分,那么丈量战略便是告诉Layout
怎样去做这件事情,这个在Layout
的注释中有阐明
Layout is the main core component for layout. It can be used to measure and position zero or more layout children. The measurement, layout and intrinsic measurement behaviours of this layout will be defined by the measurePolicy instance. See MeasurePolicy for more details.
所以不管是Column
仍是Row
,它们各自的丈量战略才是决定它们怎样去布局的关键,所以咱们进一步去columnMeasurePolicy
与rowMeasurePolicy
里边去看下它们的完成方法
咱们看到不管是rowMeasurePolicy
函数仍是columnMeasurePolicy
函数,它们都会一起走到同一个函数也便是rowColumnMeasurePolicy
,从这一点上来看,Column
与Row
生成丈量战略是在同一个当地处理的,那怎样区分是笔直仍是水平布局呢?咱们先看下rowColumnMeasurePolicy
函数的参数
orientation
榜首个参数是个LayoutOrientation
对象,这个参数表明布局的方向,大家一望而知,在上面rowMeasurePolicy
中传的是LayoutOrientation.Horizontal
,在columnMeasurePolicy
中传的是LayoutOrientation.Vertical
,这相当于现已给两个布局做了区分,而且在rowColumnMeasurePolicy
函数里边,这个orientation
也正是作为一个分界线,经过判别它来挑选不同的丈量方法,咱们后面会讲到。
arrangement
用来放置子视图,是个(Int, IntArray, LayoutDirection, Density, IntArray) -> Unit
类型的高阶函数,在这个高阶函数里边都会经过density.arrange
,而且在每一种对齐方法中重写该函数,然后进行不同的核算来放置子视图,density.arrange
函数里边的参数都是在rowColumnMeasurePolicy
里边核算出来的,别离代表的意义是
- totalSize:Int类型,表明剩下可用分配给子视图的空间巨细
- sizes:是个IntArray类型,放置一切子视图巨细的数组
- layoutDirection:Row布局里边才会用到,表明从左到右或许从右到左不同方向放置子视图的方法也不同
-
outPositions:同样也是一个IntArray类型的数组,差异于
sizes
,outPositions
里边放的都是每一个子视图起点方位的坐标,榜首个子视图为0,第二个的方位便是加上榜首个子视图的size,以此类推
arrangementSpacing
这个很简单懂,子视图之间的距离,经过Arrangement.spacedBy
设置进去,默许为0.
crossAxisAlignment
纵轴的对齐方法,什么是纵轴?无论是Column
仍是Row
,咱们把子视图的线性方向称为主轴,比方Column
的主轴方向便是笔直方向,与此相对的,另一个方向称为纵轴,纵轴多数体现出一个布局的巨细,比方Column
的纵轴上只给它设置1dp的巨细,那么它相当于便是一条分割线,咱们在rowMeasurePolicy
与columnMeasurePolicy
中能够知道,Column
的默许纵轴的对齐方法是Alignment.Start
,Row
的默许纵轴对齐方法是Alignment.Top
crossAxisSize
纵轴的尺度形式,是个SizeMode
的枚举类型,正如刚刚介绍纵轴的对齐方法提到的,纵轴体现出一个布局的巨细,而这个SizeMode
的尺度形式便是设定这个巨细的形式,它有两个值
- SizeMode.Wrap:默许值,自适应于子视图的巨细
- SizeMode.Expand:纵轴方向上占满父布局的剩下空间
知道rowColumnMeasurePolicy
函数每一个参数的含义,咱们再去看这个函数里边的代码完成就便利多了,这个函数的进口便是确认了视图主轴与纵轴的巨细核算方法,这儿每一个Placeable
都保存着一个子视图的丈量成果
这边现已开端经过orientation
来区分了,很简单了解,水平方向的主轴巨细便是这个Placeble
的width,而笔直方向主轴的巨细便是这个Placeable
的height,再往下看,代码直接就回来了一个MeasurePolicy
的对象,在MeasurePolicy
的MeasureScope.measure
进行了各种坐标的丈量核算,最终得出的便是刚刚提到的Density.arrange
里边的参数
经过MeasureScope
的layout
函数回来一个MeasureResult
的对象,其间layout
函数便是用来设置子视图的巨细,alignment lines,以及详细的放置子视图的逻辑,layout
函数的代码完成如下所示
其间决定子视图的方位是经过placementBlock
这个lambda表达式来设置,回到MeasureScope.measure
调用layout函数的当地,咱们看到在placementBlock
这个lambda表达式里边就履行了咱们刚刚提到的arrangement
函数,也就相当于履行了Density.arrange
,而Density.arrange
函数详细是做什么事情的呢?是去给mainAxisPositions
这个存放主轴的坐标数组赋值的,由于这个数组初始值便是个0的IntArray数组
而赋值的当地就依赖于主轴方向上的对齐方法,比方Column
的默许主轴对齐方法为Arrangement.Top
,那么在Arrangement.Top
里边便是这么赋值的
size
便是每个子视图主轴上巨细的数组,循环遍历后将每一次累加后的值赋给outPosition
的对应下标中,那么最终outPosition
里边便是每一个子视图开端布局的开始主轴坐标组成的数组,那么主轴的赋值完了,接下去是纵轴,纵轴简略多了,由于不必依赖于对齐方法,它的赋值操作就在arrangement
函数下面
这儿边首要对子视图丈量成果的数组placeables
进行了一次遍历,在遍历过程中就确认了纵轴坐标crossAxis
,到了这儿,一个子视图的主轴,纵轴坐标现已确认好了,接下去便是放置子视图了,代码中在确认好了纵轴坐标今后,也是直接走到了placeable
的place
函数里边,在这个函数里边便是将核算好主纵轴坐标的子视图放置到父布局的坐标系里边。
代码剖析到了这儿,咱们现已知道了为什么Column
是笔直布局,Row
是水平布局的了,便是经过界说各自的orientation
以及对齐方法之后,核算每个子视图的主纵轴坐标,最终假如orientation
是Horizontal
的,那么主轴坐标是沿着父坐标系的x轴方向,否则,便是沿着父坐标系y轴方向
问题六:Surface为什么不能像Box那样运用Modifier.background设置布景色彩?
咱们知道Box
与Surface
都是帧布局,可是假如以为已然都是帧布局,那么运用起来也必定相同的话,那么或许会造成一些不明所以的问题,比方下面这个比方
咱们看到上方左图中有段代码,一个Column
布局里边放着一个Box
布局和一个Surface
布局,而且别离对这俩布局设置相同的尺度以及布景色彩,可是从上方右图中能够发现,设置的特点在Box
里边都收效了,可是在Surface
里边却没有收效,究竟是布景色没有收效仍是设置的尺度没有收效呢?咱们更改下Column
的布景色再看看
原来是给Surface
设置Modifier.background
的时分无效了,为什么呢?咱们进到Surface
里边去找找原因
首要看到的是Surface
的一些参数,从上面的注释中咱们能够知道这些参数是干什么用的,有能够设置形状款式的,有能够设置暗影的,也有能够设置边框的,其间咱们需要看的是color
跟contentColor
两个参数,contentColor
简略来说便是给Surface
的子视图设置布景色彩的,假如没有值,就默许取参数color
的值,而color
默许值是MaterialTheme.colors.surface
,这个是啥?咱们先不看,只需要暂时知道这个是color
的默许值就好,继续走到Surface
里边看它的完成原理
总算找到原因了,在榜首个绿框子里边首要对background
进行了判别并赋值,假如color
被赋值了,就优先取color
,否则布景色便是默许值MaterialTheme.colors.surface
,然后在第二个绿框子里边,直接将backgroundColor
赋值给了Modifier.background
特点,所以在上层怎样设置Modifier.background
也没用,由于Surface
早现已默许将color
作为Modifier.background
的取值。然后咱们看下为什么MaterialTheme.colors.surface
这个默许值是白色,MaterialTheme.colors
里边是Compose库自己界说的一些主题色彩,别离适用于各种场景,而surface
便是用来设置比方卡片,菜单这姿态组件的布景色彩
而给surface
赋值的当地就在函数lightColors
里边
咱们看到surface
的默许值便是设置为白色,相对的在另一个函数darkColors
里边也对surface
做了赋值,这两个函数别离都是白日形式与黑夜形式下走的函数,而咱们的调试环境便是白日形式,所以surface
最终的色值便是白色,也便是咱们在比方中出现出来的姿态,现在咱们修改下上面的比方,将Surface
的布景色彩运用color
去设置,Surface
的布景色立马就出来了
问题七:除了运用翻滚组件,还有什么办法能够让一个页面上下滑动
能够运用线性布局的修饰符自带的scroll函数来完成页面上下滑动,下面咱们就用这种方法来完成一个简略的可滑动的页面。
这种做法就相当于传统View里边的ScrollView控件,然后在ColumnScope里边累加子视图就能够了,咱们这儿写个循环函数,函数里边生成咱们的子视图
generateItem里边是一个Surface布局,在布局里边咱们运用ConstraintLayout结构一个左边头像,右边是阐明文的布局,代码如下
运行一下作用图如下所示
不过这种方法不引荐运用,究竟一会儿生成那么多视图对内存开支仍是比较大的,简单内存走漏
问题八:什么是固有特性丈量?它是干什么用的?
Compose里边视图的绘制流程跟传统View的绘制流程是一样的,也是三步,别离是
- 组合:履行Compose函数题,并生成LayoutNode
- 布局:关于每一个LayoutNode进行宽高丈量并完成方位摆放
- 绘制:将一切LayoutNode绘制到屏幕上
其间在布局这一步中,每一个LayoutNode
都会依据自己的父LayoutNode
的束缚进行自我丈量,束缚里边包括答应的最大最小宽高,子LayoutNode
在正式丈量的时分,最大最小宽高是不能超出父LayoutNode
给予的束缚的,有点绕,其实很常见,举个比便利利了解一点
比方这儿有两个TextField
,输入内容的时分假如内容长度超越TextField
的宽度了,就会换行,TextField
高度就会增大,代码如下
这个时分,有个需求是这么要求的,在两个TextField
中间放一根分割线,期望分割线的高度永远与两个TextField
中最大的一个保持一致,而且跟着高度改变而改变,那么首要就能够排除将分割线的高度设置为固定的值,否则没法跟着TextField
高度改变而改变,那么换一个思路考虑,与最高的TextField
高度一致,也便是占满父布局的高度,那么咱们能够运用Modifier.fillMaxHeight()
操作符来占满父布局高度,修改后的代码如下
但这样做咱们在预览作用中是这样的
怎样会这样呢?很明显,由于关于Divider
来讲,它的父布局给它的最大高度束缚便是整个屏幕的高度,所以当Divider
设置Modifier.fillMaxHeight()
今后,它的高就直接被设置成了屏幕的高度,这个时分咱们就需要运用固有特性丈量来解决这个问题,那什么是固有特性丈量呢,它便是给咱们供给了预先丈量一切子LayoutNode确认本身宽高的能力,并在正式丈量中对子LayoutNode的丈量产生影响,那怎样运用呢,只需要为父布局的高度设置固有特性丈量即可,由于咱们Modifier.height不仅能够传固定高度,还能够传一个IntrinsicSize的枚举值
所以咱们在上述代码中加入这个固有特性丈量的设置,就变成了
加了这个IntrinsicSize.Min
是什么意思呢?意思便是让父布局也便是Row
依据子视图的信息进行一次核算从而确认个最小高度值,这样当Divider
设置Modifier.fillMaxHeight()
的时分,高度永远是那个核算出来的最小高度值,但也不是说设置了IntrinsicSize
参数就必定能运用固有特性丈量,还必须满意一个条件便是父布局现已适配了固有特性丈量,怎样适配呢?还记得咱们在问题五中提到的MeasurePolicy
吗?这个丈量战略除了完成它的measure
函数之外,还完成了其他四个函数
只有完成了这四个函数,才能够运用固定特性丈量,现在组件库里边的组件基本都满意了这个条件,可是在自界说视图的时分,咱们要记住在重写了MeasurePolicy
的measure
函数今后,也不要忘掉重写这四个函数,否则是无法运用固有特性丈量的,它们代表的意义如下:
-
IntrinsicMeasureScope.minIntrinsicWidth:表明布局能够接受的最小宽度,上层调用
Modifier.width(IntrinsicSize.Min)
来运用这个设置。 -
IntrinsicMeasureScope.minIntrinsicHeight:表明布局能够接受的最小高度,上层调用
Modifier.height(IntrinsicSize.Min)
来运用这个设置。 -
IntrinsicMeasureScope.maxIntrinsicWidth:表明布局宽度增长的上限,上层调用
Modifier.width(IntrinsicSize.Max)
来运用这个设置。 -
IntrinsicMeasureScope.maxIntrinsicHeight:表明布局高度增长的上限,上层调用
Modifier.height(IntrinsicSize.Max)
来运用这个设置。
现在咱们看下加了固有特性丈量的作用
这一会儿分割线就变得“有智商”了是不,这便是固有特性丈量带来的优点。
总结
写这个文章之前其实有些常识点我也不清楚,比方固有特性丈量,本来也没打算写,也是看到了MeasurePolicy有这些重写函数今后,再去看源码,翻资料才知道这是个啥东西,其实关于咱们这些用惯了传统Android视图系统的人来讲,在刚接触Compose或许在运用Compose做日常开发的时分,必定多多少少有些当地会不太适应,但只需静下心来多看看源码,尝试着多问几个“为什么”,这套新的ui系统也是能够做到真的娴熟通晓的。