這篇文章主要講解一個(gè)簡(jiǎn)單列表的實(shí)現(xiàn)费奸,包括如何自定義列表中的每個(gè)條目, 利用 RecyclerCollectionComponent 組件以及 Sections 庫(kù)來創(chuàng)建列表谢谦,如何自定義每個(gè)組件的屬性释牺。
第一個(gè)自定義組件
首先我們先來定義列表中的條目,每個(gè)條目包含一個(gè)主標(biāo)題和副標(biāo)題回挽,Litho 的預(yù)定義組件中并沒有這樣的組件没咙,事實(shí)上也不應(yīng)該有這樣的組件,需要我們自定義組件千劈,相當(dāng)于在 Android 系統(tǒng)中 LinearLayout 中的垂直方向擺放兩個(gè) TextView 祭刚。 Litho 中,編寫 Spec 類來聲明組件的布局墙牌,也就是編寫各種不同組件的組合涡驮,在 Spec 類上添加 @LayoutSpec 注解,編寫一個(gè)用 @OnCreateLayout 注解的方法返回需要顯示的組件喜滨,實(shí)際上用到的類是去掉 Spec 后綴的 Component 類捉捅,框架會(huì)生成代碼中真正用到的 Component 類,這里我們的自定義組件叫做 ListItem 虽风,相應(yīng)地棒口,我們要編寫 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 中的核心組件,這個(gè)例子中辜膝,我們把 Text 組件作為 Column 的子組件傳入陌凳,這里的 Column 相當(dāng)于 Android 中垂直方向的 LinearLayout ,里面設(shè)置了 padding 和 backbroundColor 兩個(gè)屬性。
那么如何使用我們剛剛編寫的這個(gè)組件内舟?
final Component component = ListItem.create(context).build();
注意:這里我們用的是 ListItem,而不是 ListItemSpec初橘。
那么 ListItem 是怎么來的验游? create() 和 build() 方法在哪里定義的充岛?
在 Hello World 中我們?cè)?gradle 文件中添加入了有關(guān)注解處理器的依賴, Litho 的注解處理器會(huì)掃描代碼耕蝉,查找 Spec 類崔梗,并生成去掉后綴 Spec 的組件類,同時(shí)自動(dòng)填充一些必要的方法垒在。
Litho 還可以實(shí)現(xiàn)類似于 LinearLayout 中的 weight 和 FrameLayout 的效果蒜魄,請(qǐng)參考 Layout 。
運(yùn)行APP场躯,效果如下:
創(chuàng)建列表
這一節(jié)我們要使用到 Litho 中的 RecyclerCollectionComponent 組件以及 Sections 庫(kù)來創(chuàng)建列表谈为。
RecyclerCollectionComponent 用于創(chuàng)建 Litho 滾動(dòng)的單元,隱藏了直接使用 Android 中 RecyclerView 和 Adapter 交互的復(fù)雜性踢关。
Sections API 可以把列表中的條目放到 Section 中伞鲫,寫 GroupSectionSpec 類來聲明每個(gè) Section 要渲染的內(nèi)容和使用的數(shù)據(jù)。
這里我們要自定義的 Section 叫做 ListSection, 因此需要聲明 ListSectionSpec 類签舞,在類上添加 @GroupSectionSpec 注解秕脓,定義 onCreateChildren 方法,返回需要渲染的子 Section 們儒搭,這里每個(gè)子 Section 顯示一個(gè) 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 提供中的一個(gè)核心 Section,定義在 com.facebook.litho.sections.widget 這個(gè)包中搂鲫,只不過這個(gè) Section 負(fù)責(zé)渲染一個(gè)單一的 Component 傍药。ListSectionSpec 描述了一個(gè)包含有 32 個(gè)子 Section 的 Section ,這 32 個(gè)子 Section 中默穴,每個(gè) Section 負(fù)責(zé)渲染一個(gè) ListItem 組件怔檩。
這里定義的是 Section ,并沒有定義 Component蓄诽,那么如何把 Section 顯示在屏幕上薛训?
把 Activity 中組件的定義改成下面的代碼:
final Component component =
RecyclerCollectionComponent.create(context)
.disablePTR(true)
.section(ListSection.create(new SectionContext(context)).build())
.build();
注意:這里使用的是 ListSection ,而不是 ListSectionSpec 仑氛。
解釋:
這里我們用 RecyclerCollectionComponent 這個(gè)組件乙埃,把剛剛定義的Section 顯示在屏幕上。 RecyclerCollectionComponent 接收一個(gè) Section 作為屬性锯岖,會(huì)渲染一個(gè) RecyclerView 顯示 Section中的內(nèi)容介袜。它來管理數(shù)據(jù)刷新的操作,這里不使用下拉刷新功能出吹,所以 通過設(shè)置 .disablePTR(true) 把這個(gè)功能關(guān)掉遇伞。
運(yùn)行代碼,效果如下:
定義組件的屬性
上面的列表中捶牢,我們所有的列表項(xiàng)都顯示重復(fù)的內(nèi)容鸠珠,現(xiàn)在我們想要列表中的內(nèi)容是變化的巍耗。
這里引入 Litho 中屬性的概念,也就是 Prop 渐排。Component 的屬性就是 Component Spec 類(這個(gè)類是我們編寫用于生成對(duì)應(yīng)的 Component 類的)中方法的參數(shù)炬太,這些參數(shù)上帶有 @Prop 注解。
把 ListItemSpec 進(jìn)行如下修改:
@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();
}
解釋:
這里我們添加了三個(gè)屬性驯耻,color亲族,title,subtitle 可缚。這里的 backgroundColor 以及 Text 組件的 文本內(nèi)容不再是硬編碼的形式霎迫,而是根據(jù) onCreateLayout 方法中的參數(shù)給出。
Litho 的注解處理器會(huì)根據(jù) @Prop 注解城看,為注解的參數(shù)生成相應(yīng)的構(gòu)造器方法女气,例如這里參數(shù)名稱是 color,就會(huì)為 ListItem 生成 color(int) 方法测柠,相應(yīng)地炼鞠,這里還會(huì)生成另外兩個(gè)構(gòu)造器方法,title(String) ,subtitle(String) 轰胁。在創(chuàng)建 ListItem 組件的時(shí)候谒主,就需要在構(gòu)造器方法中,對(duì)屬性進(jìn)行賦值赃阀。
修改 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();
}
另外霎肯,屬性上還可以有其他選項(xiàng),例如:
@Prop(optional = true, resType = ResType.DIMEN_OFFSET) int shadowRadius,
注解處理器還會(huì)生成一些對(duì)應(yīng)的方法 :shadowRadiusPx, shadowRadiusDip, shadowRadiusSp 以及 shadowRadiusRes榛斯。
注意:
- Prop 可以被 Spec 中不同的生命周期方法訪問观游,只需要在相應(yīng)的方法參數(shù)上添加這個(gè) Prop ,Litho 保證每個(gè)方法訪問到的屬性值是一致的驮俗,但是要保證同一個(gè)Prop 在不同方法中的聲明完全一致懂缕,比如 方法1 中 使用 @Prop(optional = true) String prop1,那么 方法2 中也要采用 @Prop(optional = true) String prop1, 否則注解處理器會(huì)報(bào)錯(cuò)。
- 另外對(duì)于 optional = true 的屬性王凑,在組件創(chuàng)建時(shí)可不傳入該屬性的值搪柑,如果未添加此項(xiàng)設(shè)置,則會(huì)在運(yùn)行時(shí)報(bào)如下錯(cuò)誤:下面是我把上面代碼中的 subtitle一樣注釋掉報(bào)的錯(cuò):
java.lang.IllegalStateException: The following props are not marked as optional and were not supplied: [subtitle]
有關(guān)屬性的問題索烹,請(qǐng)參考 Props
運(yùn)行APP工碾,會(huì)看到如下效果: