這篇文章主要講解一個簡單列表的實現(xiàn),包括如何自定義列表中的每個條目, 利用 RecyclerCollectionComponent 組件以及 Sections 庫來創(chuàng)建列表,如何自定義每個組件的屬性。
第一個自定義組件
首先我們先來定義列表中的條目,每個條目包含一個主標題和副標題,Litho 的預定義組件中并沒有這樣的組件,事實上也不應該有這樣的組件,需要我們自定義組件,相當于在 Android 系統(tǒng)中 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 還可以實現(xiàn)類似于 LinearLayout 中的 weight 和 FrameLayout 的效果,請參考 Layout 。
運行APP,效果如下:

創(chuàng)建列表
這一節(jié)我們要使用到 Litho 中的 RecyclerCollectionComponent 組件以及 Sections 庫來創(chuàng)建列表。
RecyclerCollectionComponent 用于創(chuàng)建 Litho 滾動的單元,隱藏了直接使用 Android 中 RecyclerView 和 Adapter 交互的復雜性。
Sections API 可以把列表中的條目放到 Section 中,寫 GroupSectionSpec 類來聲明每個 Section 要渲染的內(nèi)容和使用的數(shù)據(jù)。
這里我們要自定義的 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中的內(nèi)容。它來管理數(shù)據(jù)刷新的操作,這里不使用下拉刷新功能,所以 通過設置 .disablePTR(true) 把這個功能關掉。
運行代碼,效果如下:

定義組件的屬性
上面的列表中,我們所有的列表項都顯示重復的內(nèi)容,現(xiàn)在我們想要列表中的內(nèi)容是變化的。
這里引入 Litho 中屬性的概念,也就是 Prop 。Component 的屬性就是 Component Spec 類(這個類是我們編寫用于生成對應的 Component 類的)中方法的參數(shù),這些參數(shù)上帶有 @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 組件的 文本內(nèi)容不再是硬編碼的形式,而是根據(jù) onCreateLayout 方法中的參數(shù)給出。
Litho 的注解處理器會根據(jù) @Prop 注解,為注解的參數(shù)生成相應的構(gòu)造器方法,例如這里參數(shù)名稱是 color,就會為 ListItem 生成 color(int) 方法,相應地,這里還會生成另外兩個構(gòu)造器方法,title(String) ,subtitle(String) 。在創(chuàng)建 ListItem 組件的時候,就需要在構(gòu)造器方法中,對屬性進行賦值。
修改 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 中不同的生命周期方法訪問,只需要在相應的方法參數(shù)上添加這個 Prop ,Litho 保證每個方法訪問到的屬性值是一致的,但是要保證同一個Prop 在不同方法中的聲明完全一致,比如 方法1 中 使用 @Prop(optional = true) String prop1,那么 方法2 中也要采用 @Prop(optional = true) String prop1, 否則注解處理器會報錯。
- 另外對于 optional = true 的屬性,在組件創(chuàng)建時可不傳入該屬性的值,如果未添加此項設置,則會在運行時報如下錯誤:下面是我把上面代碼中的 subtitle一樣注釋掉報的錯:
java.lang.IllegalStateException: The following props are not marked as optional and were not supplied: [subtitle]
有關屬性的問題,請參考 Props
運行APP,會看到如下效果:
