Litho学习之--列表的实现-1
这篇文章主要讲解一个简单列表的实现,包括如何自定义列表中的每个条目, 利用 RecyclerCollectionComponent 组件以及 Sections 库来创建列表,如何自定义每个组件的属性。
第一个自定义组件
首先我们先来定义列表中的条目,每个条目包含一个主标题和副标题,Litho 的预定义组件中并没有这样的组件,事实上也不应该有这样的组件,需要我们自定义组件,相当于在 Android 系统中 LinearLayout 中的垂直方向摆放两个 TextView 。 Litho 中,编写 Spec 类来声明组件的布局,也就是编写各种不同组件的组合,在 Spec 类上添加 @LayoutSpec 注解,编写一个用 @OnCreateLayout 注解的方法返回需要显示的组件,实际上用到的类是去掉 Spec 后缀的 Component 类,框架会生成代码中真正用到的 Component 类,这里我们的自定义组件叫做 ListItem ,相应地,我们要编写 ListItemSpec 类:
@LayoutSpec
public class ListItemSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c) {
return Column.create(c)
.paddingDip(ALL, 16)
.backgroundColor(Color.WHITE)
.child(
Text.create(c)
.text("Hello world")
.textSizeSp(40))
.child(
Text.create(c)
.text("Litho tutorial")
.textSizeSp(20))
.build();
}
}
解释:
这里的 Text 就是 Hello World 里面见到的 Litho 中的核心组件,这个例子中,我们把 Text 组件作为 Column 的子组件传入,这里的 Column 相当于 Android 中垂直方向的 LinearLayout ,里面设置了 padding 和 backbroundColor 两个属性。
那么如何使用我们刚刚编写的这个组件?
final Component component = ListItem.create(context).build();
注意:这里我们用的是 ListItem,而不是 ListItemSpec。
那么 ListItem 是怎么来的? create() 和 build() 方法在哪里定义的?
在 Hello World 中我们在 gradle 文件中添加入了有关注解处理器的依赖, Litho 的注解处理器会扫描代码,查找 Spec 类,并生成去掉后缀 Spec 的组件类,同时自动填充一些必要的方法。
Litho 还可以实现类似于 LinearLayout 中的 weight 和 FrameLayout 的效果,请参考 Layout 。
运行APP,效果如下:
First Custom Component创建列表
这一节我们要使用到 Litho 中的 RecyclerCollectionComponent 组件以及 Sections 库来创建列表。
RecyclerCollectionComponent 用于创建 Litho 滚动的单元,隐藏了直接使用 Android 中 RecyclerView 和 Adapter 交互的复杂性。
Sections API 可以把列表中的条目放到 Section 中,写 GroupSectionSpec 类来声明每个 Section 要渲染的内容和使用的数据。
这里我们要自定义的 Section 叫做 ListSection, 因此需要声明 ListSectionSpec 类,在类上添加 @GroupSectionSpec 注解,定义 onCreateChildren 方法,返回需要渲染的子 Section 们,这里每个子 Section 显示一个 ListItem 组件。
@GroupSectionSpec
public class ListSectionSpec {
@OnCreateChildren
static Children onCreateChildren(final SectionContext c) {
Children.Builder builder = Children.create();
for (int i = 0; i < 32; i++) {
builder.child(
SingleComponentSection.create(c)
.key(String.valueOf(i))
.component(ListItem.create(c).build()));
}
return builder.build();
}
}
解释:
SingleComponentSection 是 Litho Section API 提供中的一个核心 Section,定义在 com.facebook.litho.sections.widget 这个包中,只不过这个 Section 负责渲染一个单一的 Component 。ListSectionSpec 描述了一个包含有 32 个子 Section 的 Section ,这 32 个子 Section 中,每个 Section 负责渲染一个 ListItem 组件。
这里定义的是 Section ,并没有定义 Component,那么如何把 Section 显示在屏幕上?
把 Activity 中组件的定义改成下面的代码:
final Component component =
RecyclerCollectionComponent.create(context)
.disablePTR(true)
.section(ListSection.create(new SectionContext(context)).build())
.build();
注意:这里使用的是 ListSection ,而不是 ListSectionSpec 。
解释:
这里我们用 RecyclerCollectionComponent 这个组件,把刚刚定义的Section 显示在屏幕上。 RecyclerCollectionComponent 接收一个 Section 作为属性,会渲染一个 RecyclerView 显示 Section中的内容。它来管理数据刷新的操作,这里不使用下拉刷新功能,所以 通过设置 .disablePTR(true) 把这个功能关掉。
运行代码,效果如下:
列表实现1定义组件的属性
上面的列表中,我们所有的列表项都显示重复的内容,现在我们想要列表中的内容是变化的。
这里引入 Litho 中属性的概念,也就是 Prop 。Component 的属性就是 Component Spec 类(这个类是我们编写用于生成对应的 Component 类的)中方法的参数,这些参数上带有 @Prop 注解。
把 ListItemSpec 进行如下修改:
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c,
@Prop int color,
@Prop String title,
@Prop String subtitle) {
return Column.create(c)
.paddingDip(ALL, 16)
.backgroundColor(color)
.child(
Text.create(c)
.text(title)
.textSizeSp(40))
.child(
Text.create(c)
.text(subtitle)
.textSizeSp(20))
.build();
}
解释:
这里我们添加了三个属性,color,title,subtitle 。这里的 backgroundColor 以及 Text 组件的 文本内容不再是硬编码的形式,而是根据 onCreateLayout 方法中的参数给出。
Litho 的注解处理器会根据 @Prop 注解,为注解的参数生成相应的构造器方法,例如这里参数名称是 color,就会为 ListItem 生成 color(int) 方法,相应地,这里还会生成另外两个构造器方法,title(String) ,subtitle(String) 。在创建 ListItem 组件的时候,就需要在构造器方法中,对属性进行赋值。
修改 ListSection 中的方法:
@OnCreateChildren
static Children onCreateChildren(final SectionContext c) {
Children.Builder builder = Children.create();
for (int i = 0; i < 32; i++) {
builder.child(
SingleComponentSection.create(c)
.key(String.valueOf(i))
.component(ListItem.create(c)
.color(i % 2 == 0 ? Color.WHITE : Color.LTGRAY)
.title(i + ". Hello, world!")
.subtitle("Litho tutorial")
.build()));
}
return builder.build();
}
另外,属性上还可以有其他选项,例如:
@Prop(optional = true, resType = ResType.DIMEN_OFFSET) int shadowRadius,
注解处理器还会生成一些对应的方法 :shadowRadiusPx, shadowRadiusDip, shadowRadiusSp 以及 shadowRadiusRes。
注意:
- Prop 可以被 Spec 中不同的生命周期方法访问,只需要在相应的方法参数上添加这个 Prop ,Litho 保证每个方法访问到的属性值是一致的,但是要保证同一个Prop 在不同方法中的声明完全一致,比如 方法1 中 使用 @Prop(optional = true) String prop1,那么 方法2 中也要采用 @Prop(optional = true) String prop1, 否则注解处理器会报错。
- 另外对于 optional = true 的属性,在组件创建时可不传入该属性的值,如果未添加此项设置,则会在运行时报如下错误:下面是我把上面代码中的 subtitle一样注释掉报的错:
java.lang.IllegalStateException: The following props are not marked as optional and were not supplied: [subtitle]
有关属性的问题,请参考 Props
运行APP,会看到如下效果:
列表实现-带属性