购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 Modifier修饰符

在传统开发中,使用XML文件来描述组件的样式,而Jetpack Compose设计了一个精妙的东西,它叫作Modifier。

Modifier允许我们通过链式调用的写法来为组件应用一系列的样式设置,如边距、字体、位移等。在Compose中,每个基础的Composable组件都有一个modifier参数,通过传入自定义的Modifier来修改组件的样式。

2.1.1 常用修饰符

本节介绍一些预置的Modifier修饰符,它们对于所有Composable组件都通用,也是我们平常接触最多的。

1.Modifier.size

先来介绍最常用的size修饰符,它用来设置被修饰组件的大小。

上面的代码运行结果如图2-1所示。

•图2-1 size修饰符

size同时提供了重载方法,支持单独设置组件的宽度与高度。

2.Modifier.background

backgroud修饰符用来为被修饰组件添加背景色。背景色支持设置color的纯色背景,也可以使用brush设置渐变色背景。Brush是Compose提供的用来创建线性渐变色的工具。

代码执行结果如图2-2所示。

•图2-2 background修饰符

补充提示:

传统视图中View的background属性可以用来设置图片格式的背景,Compose的background修饰符只能设置颜色背景,图片背景需要使用Box布局配合Image组件实现。关于这些组件将在本章后面的内容中介绍。

3.Modifier.fillMaxSize

前面我们介绍了size方法,但是有的时候想要让组件在高度或者宽度上填满父空间,此时可以使用fillMaxXXX系列方法:

代码执行结果如图2-3所示。

•图2-3 fillMaxSize、fillMaxHeight、fillMaxWidth修饰符

4.Modifier.border & Modifier.padding

border用来为被修饰组件添加边框。边框可以指定颜色、粗细,以及通过Shape指定形状,比如圆角矩形等。padding用来为被修饰组件增加间隙。可以在border前后各插入一个padding,区分对外和对内的间距,代码如下:

执行结果如图2-4所示。

•图2-4 boder、padding修饰符

补充提示:

相对于传统布局有Margin和Padding之分,Compose中只有padding这一种修饰符,根据在调用链中的位置不同发挥不同作用,概念更加简洁,这也体现了Modifier中链式调用的特点。

5.Modifier.offset

offset修饰符用来移动被修饰组件的位置,我们在使用时只分别传入水平方向与垂直方向的偏移量即可。

注意:

Modifier调用顺序会影响最终UI呈现的效果,这里应使用offset修饰符偏移,再使用background修饰符绘制背景色。

也可以使用offset的重载方法,返回一个IntOffset实例:

代码运行后如图2-5所示。

•图2-5 offset修饰符

2.1.2 作用域限定Modifier修饰符

Compose充分发挥了Kotlin的语法特性,让某些Modifier修饰符只能在特定 作用域 中使用,有利于类型安全地调用它们。所谓的“作用域”,在Kotlin中就是一个带有Receiver的代码块。例如Box组件参数中的conent就是一个Reciever类型为BoxScope的代码块,因此其子组件都处于BoxScope作用域中。

需要注意Reciever类型默认可以跨层级访问。例如下面的例子中,由于funB{...}处于funA{...}内部,可以在funB{...}中访问到属于funA{...}的方法visitA()。

在Compose的DSL中,一般只需要调用当前作用域的方法,像上面这样的Receiver跨级访问会成为写代码时的“噪声”,加大出错的概率。Compose考虑到了这个问题,可以通过@LayoutScopeMarker注解来规避Receiver的跨级访问。常用组件Receivier作用域类型均已使用@LayoutScopeMarker注解进行了声明。

对于添加了此注解的Receiver,我们在其作用域中默认只能调用作用域提供的方法。像跨级调用外层作用域的方法时,必须通过显式指明Receiver具体类型。

补充提示:

@LayoutScopeMarker的能力其实来自@DslMarker,这是Kotlin专门为DSL场景提供的元注解。经过@DslMarker定义的注解,在DSL中使用时可以规避跨级访问。注意,不同的@DslMarker注解之间没有此效果。

作用域限定修饰符的好处在于类型安全,这在传统视图中是难以保证的。例如可以在XML中为LinearLayout的子组件设置android::toRightOf属性,这对于父布局没有任何意义,有时可能还会造成问题,然而我们很难做到根据不同父类型安全地调用不同方法。 Compose作用域限定实现了Modifier的安全调用,我们只能在特定作用域中调用修饰符 ,就像只能在FrameLayout内使用toRightOf一样,如果换作LinearLayout将无法设置toRightOf。接下来简单认识几个常见的作用域限定Modifier修饰符。

1.matchParentSize

matchParentSize是只能在BoxScope中使用的作用域限定修饰符。当使用matchParentSize设置尺寸时,可以保证当前组件的尺寸与父组件相同。而父组件默认的是wrapContent,会根据UserInfo的尺寸确定自身的尺寸。代码执行后如图2-6所示。

•图2-6 matchParentSize修饰符

如果使用fillMaxSize取代matchParentSize,那么该组件的尺寸会被设置为父组件所允许的最大尺寸,这样会导致背景铺满整个屏幕。代码执行后如图2-7所示。

•图2-7 fillMaxSize修饰符

2.weight

在RowScope与ColumnScope中,可以使用专属的weight修饰符来设置尺寸。与size修饰符不同的是,weight修饰符允许组件通过百分比设置尺寸,也就是允许组件可以自适应适配各种屏幕尺寸的移动终端设备。

例如,我们希望白色方块、蓝色方块与红色方块共享一整块Column空间,其中每种颜色方块高度各占比1/3,如图2-8所示。使用weight修饰符可以很容易地实现,代码如下。

•图2-8 weight修饰符

2.1.3 Modifier实现原理

前面我们提到Modifier调用顺序会影响最终UI的呈现效果。这是因为Modifier会由于调用顺序不同而产生出不同的Modifier链,Compose会按照Modifier链来顺序完成页面测量布局与渲染。那么Modifier链是如何被构建并解析的呢?本小节将带领大家深入理解Modifier链背后的实现原理。

1.接口实现

从源码中我们发现Modifier实际是一个接口。它有三个具体实现,分别是一个Modifier伴生对象,Modifier. Element以及CombinedModifier,如图2-9所示。

•图2-9 Modifier接口实现

Modifier伴生对象是我们对Modifier修饰符进行链式调用的起点,即Modifier.xxx()中开头的那个Modifier。CombinedModifier用于连接Modifier链中的每个Modifier对象。Modifier. Element代表具体的修饰符。当我们使用Modifier.xxx()时,其内部实际上会创建一个Modifier实例。以size为例,其内部会创建SizeModifier实例,并使用then进行连接。

SizeModifier实现了LayoutModifier接口,而LayoutModifier是Modifier. Element的子接口,如图2-10所示。

•图2-10 Modifier继承关系

我们创建的各种Modifier本质上都是一个Modifier. Element。像LayoutModifier这类直接继承自Modifier. Element的接口,这里暂且称它们为Base Modifier。Base Modifier种类很多,如表2-1所示。

表2-1 Base Modifier列表

2.链的构建

前面提到过,Modifier.size()内部会创建一个SizeModifier实例,并使用then进行连接。then返回一个CombinedModifier,后者用来连接两个Modifier. Element。

CombinedModifier连接的两个Modifier分别存储在outer与inner中,从CombinedModifier的数据结构可以联想到,Compose对Modifier的遍历,就像剥洋葱一样从外(outer)到内(inner)一层层访问。需要注意的是,outer与inner作为private属性不能被外部直接访问,Modifier专门提供了foldOut()与foldIn()用来遍历Modifier链,这部分我们马上就会讲到。接下来通过例子理解一下Modifier链的构建:

假设从Modifier伴生对象为起点开始构造一个调用链,首先设置size。作为链上的第一个修饰符,此时的this.then的this指向作为起点的Modifier伴生对象,then直接返回SizeModifier,此时链的结构如图2-11所示。

•图2-11 链的结构

接着,通过Modifier.background为组件增加背景色。Modifier.background内部使用then连接了SizeModifier和Background, Background是DrawModifier的子类。此时Modifier链的整体数据结构如图2-12所示。

•图2-12 Modifier链的整体数据结构

接着,调用Modifier.padding(10.dp)为其设置内边距,此时padding内部使用的this指针指向的是CombinedModifier实例,通过then连接了一个PaddingModifier实例,如图2-13所示。

•图2-13 当前this指向Combined Modifier实例

最后,添加一些手势监听,通常使用Modifier.pointerInput()来定制手势处理。pointerInput内部并没有直接调用then,而是调用了composed方法。深入研究composed()后发现,其背后仍然使用then()来进行连接,此时连接的是一个ComposedModifier实例,如图2-14所示。

•图2-14 Composed Modifier实例

补充提示:

composed是then之外另一个用来连接Modifier的方法。它接受一个@Composable的factory参数,可以调用remember等方法来保存状态,因此factory可以用来创建Stateful的Modifier,例如例子中的SuspendingPointerInputFilter。

到此为止,我们知道了一个Modifier链是如何构成的,随着调用修饰符方法越多,链就会越长,这里就不再赘述了。

3.链的解析

Compose在绘制UI时,会遍历Modifier链获取配置信息。Compose使用foldOut()与foldIn()遍历Modifier链,就像Kotlin集合的fold操作符一样,链上的所有节点被“折叠”成一个结果后,传入视图树用于渲染。

foldIn和foldOut的方法相同:initial是折叠计算的初始值,operation是具体计算方法。Element参数表示当前遍历到的Modifier,返回值也是R类型,表示本轮计算的结果,会作为下一轮R类型参数传入。

folIn和foldOut的遍历顺序有所不同,假设Modifier调用顺序如下:

foldIn()代表从正向遍历:SizeModifier-> Background-> PaddingModifier-> ComposedModifier,而foldOut是反向遍历,即ComposedModifier-> PaddingModifier-> Background-> SizeModifier。

我们以Layout Composable为例最后Compose处理Modifier链的时机。很多Compose组件都是基于Layout实现的,为这些组件添加的修饰符最终也会作为参数传入Layout组件。有关Layout组件在第5章还会专门介绍。

在Layout的实现中我们看到,构建的Modifier实例被传入一个名为materializerOf的方法。继续深入学习,我们会走进Composer.materialize()。

在materialize中出现了foldIn的调用,当Modifier链中包含ComposedModifier时,也会在这里被摊平,将工厂生产Modifier加入到链中,最终链中将不再有ComposedModifier。

Modifier链后续还会使用foldOut方法进行遍历,生成LayoutNodeWrapper链来间接影响组件的测量布局与渲染,由于不是本书的重点,这里就不赘述了。 XZlOkG3CIKM5sRG7cwYao6+1lJXDBzKaoR/P6dUblrSchIhcYp8kYZ+A7PAT12if

点击中间区域
呼出菜单
上一章
目录
下一章
×