Facebook出品的Android声明式开源新框架Litho文档翻译-RTL

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

兼容性

RTL


Litho中对RTL(从右到左)布局的支持与Android的RTL支持相同.为了使你的Component支持RTL,你只需要简单的在margin和padding参数中使用START和END代替原来的LEFT和RIGHT就可以了.所有其他的工作都会由布局系统自动的完成.

举例来说,这是一个布局:

1
2
3
4
5
6
Column.create(c)
.paddingDip(START, 10)
.marginDip(END, 5)
.child(...)
.child(...)
.build();

布局系统将会自动的遵循Android资源系统定义的布局方向.你还可以使用类似的start/end变量到位置参数中来是绝对位置支持RTL.

1
2
3
4
5
6
Image.create(c)
.srcRes(R.drawable.my_image)
.withLayout()
.positionType(ABSOLUTE)
.positionDip(START, 10)
.build();

在上面的示例中,当RTL被激活时,image Component将会自动的调整到距离它的父级的右边缘10像素的位置上.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-无障碍环境

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

兼容性

无障碍环境


内容描述


所有的Component都默认支持内容描述.这意味着所有的布局builder都拥有一个CharSequence类型的prop叫做contentDescription.

在任何component上设置内容描述都非常的简单:

1
2
3
4
5
Image.create(c)
.imageRes(R.drawable.some_image)
.withLayout()
.contentDescription("This is an image")
.build())

在这里设置的内容描述与在Android view上设置的内容描述拥有同等的效果.


自定义


在Mount Spec可以通过实现一个@OnPopulateAccessibilityNode方法来实现自定义的无障碍支持.这个方法接受一个AccessibilityNodeInfoCompat参数,也接受任何在其他spec方法中指定的prop参数.

举例来说,Text的无障碍功能是使用下列方法指定的:

1
2
3
4
5
6
@OnPopulateAccessibilityNode
static void onPopulateAccessibilityNode(
AccessibilityNodeInfoCompat accessibilityNode,
@Prop CharSequence text) {
accessibilityNode.setText(text);
}

这仅适用于挂载drawable的Component,因为如果Component挂载一个view,则无障碍支持就是内置的了.


额外的无障碍节点


如果是在更复杂的需要暴露更多额外的node给无障碍框架的mount spec中,你就必须使用以下的注释实现3个额外的方法:

  • GetExtraAccessibilityNodesCount:返回Component暴露出的外的无障碍节点的个数.
  • OnpopulateExtraAccessibilityNode:使用给定的边界填充额外的无障碍节点.
  • GetExtraVirtualViewAt:返回Component中指定位置的额外无障碍节点的索引.

无障碍的处理


所有的Component都支持一系列与AccessibilityDelegateCompat方法相一致的事件.
这些事件拥有它们对应的AccessibilityDelegateCompat方法的参数,和一个额外的名叫superDelegate的AccessibilityDelegateCompat参数,它允许你在必要的时候显式的调用View的无障碍方法的默认实现.

以下是支持的事件的总览:

事件 ACCESSIBILITYDELEGATE方法
DispatchPopulateAccessibilityEventEvent dispatchPopulateAccessibilityEvent
OnInitializeAccessibilityEventEvent onInitializeAccessibilityEvent
OnInitializeAccessibilityNodeInfoEvent onInitializeAccessibilityNodeInfo
OnPopulateAccessibilityEventEvent onPopulateAccessibilityEvent
OnRequestSendAccessibilityEventEvent onRequestSendAccessibilityEvent
PerformAccessibilityActionEvent performAccessibilityAction
SendAccessibilityEventEvent sendAccessibilityEvent
SendAccessibilityUncheckedEvent sendAccessibilityEventUnchecked

设置任何这些事件的处理程序将会导致在挂载的View中设置AccessibilityDelegate,当调用相应的方法时,它将调用您的事件处理程序。

如果你没有提供处理程序的方法被调用,delegate将会交由ANdroid view的默认实现去处理(类似于调用了super或者superDelegate的实现).

举例来说,下面是覆写Component中onInitializeAccessibilityNodeInfo的步骤:
1.实现一个事件处理器:

1
2
3
4
5
6
7
8
9
@OnEvent(OnInitializeAccessiblityNodeInfoEvent.class)
static void onInitializeAccessibilityNodeInfoEvent(
@FromEvent AccessibilityDelegateCompat superDelegate,
@FromEvent View view,
@FromEvent AccessibilityNodeInfoCompat node) {
// Equivalent to calling super on a regular AccessibilityDelegate, not required
superDelegate.onInitializeAccessibilityNodeInfo(view, node);
// My implementation
}

2.把时间处理器设置到component中.

1
2
3
4
Text.create(c)
.text(title)
.withLayout()
.onInitializeAccessiblityNodeInfoHandler(MyComponent.onInitializeAccessibilityNodeInfoEvent(c))

AccessibilityDelegates的一个最好的特性之一就是它的甚至可以跨越视图的可重用性.而在Litho中,这也可以通过在Component中引入一个包含我们需要的事件处理器的包装spec来实现.例如.假设我们需要一个Component,它添加了”please”到了每一个它注释的AccessibilityEvent方法中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@LayoutSpec
class PoliteComponentWrapper {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop Component<?> content) {
return Layout.create(c, content)
.onPopulateAccessibilityEventHandler(
PoliteComponentWrapper.onPopulateAccessibilityEvent(c))
.build();
}
@OnEvent(OnPopulateAccessibilityEvent.class)
static void onPopulateAccessibilityEvent(
ComponentContext c,
@FromEvent AccessibilityDelegateCompat superDelegate,
@FromEvent View view
@FromEvent AccessibilityEvent event) {
superDelegate.onPopulateAccessibilityEvent(view, event);
event.getText().add("please");
}
}

现在你可以使用PoliteComponentWrapper来替代任何你原来使用你的Component的地方了.

1
2
3
4
5
6
7
8
9
10
11
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop CharSequence text) {
return PoliteComponentWrapper.create(c)
.content(
Text.create(c)
.text(text))
.build();
}




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-Styles

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

兼容性

Styles


Component可以像Android View的属性集的构建器一样,使用Android的style资源来构建它的prop.它让开发者能够直接使用style资源来定义静态的prop值或者prop的默认值.

可以通过在你的Component的Spec中实现@OnLoadStyle方法来支持Style.它的第一个参数是ComponentContext,你可以用它来检索生成一个包含stype资源的TypedArray.其他的参数需要是Output类型,并且名称与类型都与你想要设置的prop相同.

1
2
3
4
5
6
7
8
9
10
11
12
@LayoutSpec
class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop String prop1,
@Prop int prop2) {
return ...;
}
}

举个例子,为了对上面的MyComponent的两个prop实现style的支持,你先要按照惯例定义样式属性:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="prop1" format="string" />
<attr name="prop2" format="integer" />
<declare-styleable name="MyComponent">
<attr name="prop1" />
<attr name="prop2" />
</declare-styleable>
</resources>

接着你可以在你的@OnLoadStyle方法中收集这些属性的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@OnLoadStyle
void onLoadStyle(
ComponentContext c,
Output<String> prop1,
Output<Integer> prop2) {
final DataBoundTypedArray a =
c.obtainDataBoundAttributes(R.styleable.Text, 0);
for (int i = 0, size = a.getIndexCount(); i < size; i++) {
final int attr = a.getIndex(i);
if (attr == R.styleable.MyComponent_prop1) {
prop1.set(a.getString(attr));
} else if (attr == R.styleable.MyComponent_prop2) {
prop2.set(a.getInteger(attr));
}
}
a.recycle();
}

这样,你就可以在style中定义prop1和prop2的值了:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SomeStyle">
<item name="prop1">@string/some_string</item>
</style>
</resources>

并且在MyComponent中使用它:

1
2
3
MyComponent.create(c, 0, R.style.SomeStyle)
.prop2(10)
.build();

这样,prop1就能够从@string/some_string资源中获取值了.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-可见性的处理

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

事件处理

可见性的处理


可见范围的类型


框架现在支持4中类型过的可见性事件:

  • 可见事件:这种事件会在Component至少有1像素是可见的时候被触发.
  • 不可见事件:这种事件会在Component不再有任何像素是可见的时候被触发.
  • 聚焦可见事件:这种事件会在Component至少占据视窗一半的的时候被触发,如果Component的大小小于视窗的一半,则会在Component完全可见的时候被触发.
  • 完全展示可见事件:当整个Component在某个时间点通过视窗时,会触发此事件。


使用


可见性范围需要相关的Component支持增量式挂载

为一个Component注册可见性事件handler,你可以按照注册其他事件handler相同的步骤来操作.

下面是示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@LayoutSpec
class MyLayoutSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(ComponentContext c) {
return Column.create(c)
.alignItems(Align.STRETCH)
.child(Text.create(c)
.text("This is MY layout spec")
.withLayout()
.visibleHandler(MyLayoutSpec.onTitleVisible(c))
.invisibleHandler(MyLayoutSpec.onTitleInvisible(c)))
.focusedHandler(MyLayoutSpec.onComponentFocused(c, "someStringParam"))
.fullImpressionHandler(MyLayoutSpec.onComponentFullImpression(c)))
.build();
}
@OnEvent(VisibleEvent.class)
static void onTitleVisible(ComponentContext c) {
Log.d("VisibilityRanges", "The title entered the Visible Range");
}
@OnEvent(InvisibleEvent.class)
static void onTitleInvisible(ComponentContext c) {
Log.d("VisibilityRanges", "The title is no longer visible");
}
@OnEvent(FocusedVisibleEvent.class)
static void onComponentFocused(
ComponentContext c,
@Param String stringParam) {
Log.d(
"VisibilityRanges",
"The component is focused with param: " + contentString);
}
@OnEvent(FullImpressionVisibleEvent.class)
static void onComponentFullImpression(ComponentContext c) {
Log.d("VisibilityRanges", "The component has logged a full impression");
}
};




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-触摸事件

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

事件处理

触摸事件处理


所有的Component都支持通过框架的事件系统处理触摸事件.所有的Component都默认支持处理下列几种事件:ClickEvent(点击事件),LongClickEvent(长按事件)和TouchEvent(触摸事件).

这意味着所有的布局builder都分别拥有叫做clickHandler,longClickHandler和touchHandler的EventHandler prop.你可以在你的@OnEvent注释里把你想要处理的事件类作为参数指定出来.

举例来说,在任意一个Component指定点击handler都非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@LayoutSpec
class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop String title) {
return Text.create(c)
.text(title)
.withLayout()
.clickHandler(MyComponent.onClick(c))
.build();
}
}

并且在MyComponentSpec中的回调可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
@LayoutSpec
class MyComponentSpec {
...
@OnEvent(ClickEvent.class)
static void onClick(
ComponentContext c,
@FromEvent View view,
@Prop String someProp) {
// Handle click here.
}
}


触摸范围扩大


你可以使用布局builder中的触摸扩展API来扩展可交互的范围边界.

1
2
3
4
5
Text.create(c)
.text(title)
.withLayout()
.clickHandler(MyComponent.onClick(c))
.touchExpansionDip(ALL, 10);

在这个例子中,text Component的可点击范围比Component的所有边界(上,下,左,右)都扩展了10dip.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-事件处理概述

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

事件处理

概述


框架提供了一套多用途的API用来在事件发生的时候与Component通信.事件被声明成一个POJO并且使用@Event注释.为了方便,我们命名事件类时使用Event作为后缀.事件的类不一定必须是LayoutSpec或者MountSpec的内部类.这是因为从设计上看,Spec一般被认为是私有的概念,而事件却可以允许被多个Component使用.

1
2
3
4
@Event
public class ColorChangedEvent {
public int color;
}

在这个例子中我们假设我们有一个Component名叫ColorComponent.为了指明一个ColorComponent可以分发ColorChangedEvent事件,我们的ColorComponentSpec必须在注释中注明这一点.注明的方式是使用@MountSpec或者@LayoutSpec注释中的events参数.一个Component的注释可以分派多个事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@LayoutSpec(events = { ColorChangedEvent.class })
class ColorComponentSpec {
...
@OnCreateLayout
static ComponentLayout onCreateLayout(
Context c,
@Prop EventHandler colorChangedHandler,
@FromPrepare int color) {
...
ColorComponent.dispatchColorChangedEvent(
colorChangedHandler,
color);
...
}
}

对一个FooEvent类型的事件,将会自动生成一个对应的dispatchFooEvent方法和一个供事件回调使用的事件标识符.

dispatchFooEvent方法使用一个EventHandler作为第一个参数,它后面紧跟着的参数是你在@Event类中定义的参数的列表.我们规定为每一个你的Component暴露出的事件使用一个EventHandler prop.

在上面的例子中,ColorComponent使用一个colorChangedHandler作为prop,并且使用自动生成的dispatchColorChangedEvent()方法把ColorChangedEvent分发给它.


回调


为了处理其他Component分发过来的事件,你需要一个EventHandler实例和一个匹配的回调.

你可以使用自动生成的Component中的相对应的eventHandler factory方法来创建EventHandler实例.这个方法的名称将会和你的事件回调方法的名称相同.

你可以使用@OnEvent注释定义事件回调.@OnEvent将接受一个参数:事件类.这个方法的第一个参数必须是一个ComponentContext,框架将会为你填充它.

举例来说,下面是一个Component如何为上面声明的ColorChangedEvent定义一个handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@LayoutSpec
class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
LayoutContext c,
@Prop String someColor) {
return Column.create(c)
...
.child(
ColorComponent.create(c)
.color(someColor)
.colorChangedHandler(MyComponent.onColorChanged(c))
...
.build();
}
@OnEvent(ColorChangedEvent.class)
static void onColorChanged(
ComponentContext c,
@FromEvent int color,
@Prop String someProp) {
Log.d("MyComponent", "Color changed: " + color);
}
}

对一个或者多个回调方法的参数使用@Param注释,你可以定义动态的事件参数.如果你想要定义一个确定类型的事件的回调(例如onAvatarClicked()),但是你也想知道哪一个头像被点击了,使用@Param注释会变得非常实用.这个情况中的avatar参数将会被传递到eventHandler factory方法中.

正如你所见,@OnEvent回调可以访问所有的Component的prop,就像其他的Spec方法一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@LayoutSpec
class FacePileComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
LayoutContext c,
@Prop Uri[] faces) {
ComponentLayout.Builder builder = Column.create(c);
for (Uri face : avatarUrls) {
builder.child(
FrescoImage.create(c)
.uri(face)
.withLayout()
.clickHandler(FacePileComponent.onFaceClicked(c, face));
}
return builder.build();
}
@OnEvent(ClickEvent.class)
static void onFaceClicked(
ComponentContext c,
@Param Uri face) {
Log.d("FacePileComponent", "Face clicked: " + face);
}
}




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-Recycler

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

参考

Recycler


RecyclerView是一个Android系统中构建一个可滚动列表所需的基本的构建快.而Recycler component提供了与RecyclerView非常相似的功能,同时实现了例如后台布局和增量式挂载这样的功能.


创建一个Recycler Component


你可以像使用其他任何Litho框架中的Component一样——先构建,再把它添加到你的布局中——这样来使用Recycler。

1
2
3
4
5
6
7
8
9
@OnCreateLayout
static ComponentLayout onCreateLayout(
final ComponentContext c,
@Prop RecyclerBinder recyclerBinder) {
return Recycler.create(c)
.binder(recyclerBinder)
.buildWithLayout();
}

以上的代码渲染了一个Recycler Component,它将显示recyclerBinder中的内容.


RecyclerBinder


RecyclerBinder是使用Component操作列表式UI的切入点.它保存了列表中包含的所有Item,并且当用户滚动列表时,它会计算将会在屏幕中显示的item的布局。

RecyclerBinder作为Litho的一部分:

  • 作用类似于RecyclerView的Adapter
  • 定义在RecyclerView中使用的布局(如Linear或Grid等)
  • 会提前在后台线程中处理负责的布局计算.

让我们开始创建一个RecyclerBinder:

1
final RecyclerBinder recyclerBinder = new RecyclerBinder(c);

这样会创建一个最简单的RecyclerBinder,它会把Recycler中的内容以竖直列表的方式展现.

如果想要让Recycler使用GridLayout(表格布局),我们可以改用这个构造函数:

1
final RecyclerBinder recyclerBinder = new RecyclerBinder(c, new GridLayoutInfo(c, spanCount));

RecyclerBinder暴露了一系列API来操作将在Recycler中显示的item.

最常使用的有:

1
2
3
4
recyclerBinder.insertItemAt(position, component);
recyclerBinder.updateItemAt(position, component);
recyclerBinder.removeItemAt(position);
recyclerBinder.moveItem(fromPosition, toPosition);

RecyclerBinder的API中直接使用到Component,因为一个Component仅仅是一系列Prop的集合,我们可以提前建立任何的Component然后把布局管理工作交给RecyclerBinder.

RecyclerBinder也支持接收规定Component该如何布局的额外的信息.这些额外的信息可以通过一个ComponentInfo传递.代码如下所示:

1
2
3
4
5
6
recyclerBinder.insertItemAt(
position,
ComponentInfo.create()
.component(component)
.isSticky(true)
.build());


使用RecyclerBinder与DiffUtil协同工作


RecyclerBinder提供了与DiffUtil协同工作的方式.Litho定义了RecyclerBinderUpdateCallback这个API,它实现了ListUpdateCallback,因此可以用来发送DiffResult到RecyclerBinder.

下面是一个如何使用Litho与DiffUtil协同工作的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final ComponentRenderer<Data> mComponentRenderer = new ComponentRenderer<> {
ComponentInfo render(Data data, int idx) {
return ComponentInfo.create()
.component(
DataComponent.create(mComponentContext)
.data(data))
.build();
}
}
public void onNewData(List<Data> newData) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDataDiffCallback(mCurrentData, newData));
final RecyclerBinderUpdateCallback callback = RecyclerBinderUpdateCallback.acquire(
mCurrentData.size(),
newData,
mComponentRenderer,
mRecyclerBinder)
diffResult.dispatchUpdatesTo(callback);
callback.applyChangeset();
RecyclerBinderUpdateCallback.release(callback);
}

每当列表中需要创建新的Component或者列表的模型需要被更新的时候,ComponentRenderer都会被调用.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-Layout

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

参考

Layout(布局)


Litho使用Yoga,一种Flexbox的实现来测量Component和在屏幕上布局.如果你以前已经在web端使用过Flexbox,那么你使用Litho时将会非常熟悉.如果你更加熟悉Android普通的布局的工作方式,那么Flexbox常常会让你想起LinearLayout.

在Litho中你可以使用一个Row(行)来实现一个类似于水平的LinearLayout的布局.

1
2
3
4
Row.create(c)
.child(...)
.child(...)
.build();

或者使用一个Column(列)来实现一个类似于竖直LinearLayout的布局.

1
2
3
4
Column.create(c)
.child(...)
.child(...)
.build();

为了实现类似于LinearLayout中的weight的效果,Flexbox提供了一个叫做flexGrow()的概念.

1
2
3
4
5
6
7
8
9
10
11
12
Row.create(c)
.child(
SolidColor.create(c)
.color(RED)
.withLayout()
.flexGrow(1))
.child(
SolidColor.create(c)
.color(BLUE)
.withLayout()
.flexGrow(1))
.build();

如果你比较喜欢FrameLayout中把一个view放置到其他view的上方的效果,Flexbox中可以使用positionType(ABSOLUTE)来实现.

1
2
3
4
5
6
7
8
9
10
11
12
Row.create(c)
.child(
SolidColor.create(c)
.color(RED)
.withLayout()
.flexGrow(1))
.child(
SolidColor.create(c)
.color(BLUE)
.withLayout()
.flexGrow(1))
.build();

如果需要查看更多关于Flexbox特性的文档,你可以查阅Yoga文档或者其他任何讲述Flexbox如何工作的网络资源.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-State

欢迎转载,转载请标明出处.
英文原文文档地址: Litho-doc

参考

State(状态)


Litho Component可以包含两种类型的data:

  • prop:从父级继承并且在Component的生命周期内不能被改变.
  • state:封装实现的细节,由Component管理,并且对父级是透明的.

一个常见的需要使用State的例子是:渲染一个Checkbox(复选框).Component需要根据选中还是未选中来渲染不同的drawable,但是这是一个Component内部的信息,而他的父级不需要去关心这个状态.


声明一个Component的状态


你可以使用@State注释在spec的生命周期方法中定义一个Component的State,这与你定义Prop的方法是一样的.

定义State元素在Layout Spec和Mount Spec的生命周期方法中可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@LayoutSpec
public class CheckboxSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@State boolean isChecked) {
return Column.create(c)
.child(Image.create(c)
.srcRes(isChecked
? R.drawable.is_checked
: R.drawable.is_unchecked))
.child(Text.create(c)
.text("Submit")
.clickHandler(Checkbox.onClickedText(c))
.build;
}
@OnEvent(ClickEvent.class)
static void onClickedText(
ComponentContext c,
@State boolean isChecked) {
...
}
}


初始化State的值


为了给State设置一个初始值,你需要在你的Spec中使用@OnCreateInitialState注释来编写一个方法.

关于编写@OnCreateInitialState方法,你需要知道以下几点:

  • 第一个参数必须是ComponentContext类型.
  • @Prop参数在这里也是可用的.
  • 剩下的参数的名称必须和其他生命周期方法中的@State参数保持一致,并且这些剩下参数的类型必须为StateValue,其中泛型的类型与对应的@State一致.
  • @OnCreateInitialState 方法不是必须的.如果你不定义或者没有定义所有state的初始值,那么那些没有被定义初始值的state将会使用java的默认值.
  • 每一个Component的@OnCreateInitialState方法只会在Component第一次被添加到Component树的时候被调用一次.如果Component的key没有改变,那么后续对Component树布局的重新计算不会重新调用@OnCreateInitialState方法.
  • 你永远不需要自己调用@OnCreateInitialState方法.

下面是例子是如何使用父级传下来的值来初始化一个复选框的state值.

1
2
3
4
5
6
7
8
9
10
11
12
@LayoutSpec
public class CheckboxSpec {
@OnCreateInitialState
static void createInitialState(
ComponentContext c,
StateValue<Boolean> isChecked,
@Prop boolean initChecked) {
isChecked.set(initChecked);
}
}


定义State的更新


你可以使用@OnUpdateState在Spec中声明一个方法来定义Component的State更新的方式.

你可以根据你想更新的state或者你的state依赖的参数的值来定义任意多的你需要的@OnUpdateState方法.

每次对@OnUpdateState方法的调用都会触发一次新的对它的Component树的布局计算.为了获得更好的性能,如果在某种情况下会触发对多个State的更新,那么你就应该定义一个@OnUpdateMethod方法来更新所有这些state的值.合并这些更新操作可以减少新的布局计算的次数,从而提升性能.

关于编写@OnUpdateState方法,你需要知道以下几点:

  • 代表State的参数名必须和其他方法中使用@State定义的参数名一样,并且类型必须为StateValue,泛型类型也必须和对应的@State参数的类型一致.
  • @Param参数也是可以使用的.如果你的State的值需要依赖于Prop,你可以在@OnupdateState函数的参数中使用@Param声明,这样就可以在更新被触发的时候传递prop的值进来了.
  • 所有其他的参数都必须在其他生命周期方法中有一个对应的@State参数,并且类型必须为StateValue,泛型的类型也必须和@State参数一致.

下面是如何给checkbox定义一个state方法:

1
2
3
4
5
6
7
8
@LayoutSpec
public class CheckboxSpec {
@OnUpdateState
static void updateCheckboxState(StateValue<Boolean> isChecked) {
isChecked.set(!isChecked.get());
}
}

如果你想要合并多个state更新到一个方法中,你只需要把所有这些state都添加到一个@OnUpdateState方法中:

1
2
3
4
5
6
7
8
9
10
@OnUpdateState
static void updateMultipleStates(
StateValue<Boolean> stateOne,
StateValue<String> stateTwo,
@Param int someParam) {
final boolean thresholdReached = someParam > 100;
stateOne.set(thresholdReached);
stateTwo.set(thresholdReached ? "reached" : "not reached");
}


调用State更新


对你的Spec中的每一个@OnUpdateState方法,自动生成的Component中都会包含两个方法,他们在后台都会委托给@OnUpdateState方法处理.

  • 一个和@OnUpdateState同名的静态方法,它将同步的应用state的更新.
  • 一个和@OnUpdateState同名并且加上Async后缀的静态方法,它将异步的触发State的更新.这两个方法都使用ComponentContext作为第一个参数,后面的参数与你在@OnUpdateState方法中使用@Param定义的参数相同.

下面展示了当用户点击的时候如何调用State更新方法来更新你的复选框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@LayoutSpec
public class CheckboxSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@State boolean isChecked) {
return Column.create(c)
.child(Image.create(c)
.srcRes(isChecked
? R.drawable.is_checked
: R.drawable.is_unchecked))
.clickHandler(Checkbox.onCheckboxClicked(c)))
.build;
}
@OnUpdateState
static void updateCheckbox(StateValue<Boolean> isChecked) {
isChecked.set(!isChecked.get());
}
@OnEvent(ClickEvent.class)
static void onCheckboxClicked(ComponentContext c) {
Checkbox.updateCheckboxAsync(c);
// Checkbox.updateCheckbox(c); for a sync update
}
}

当你调用State更新方法的时候,你需要记住下面几点:

  • 当调用一个State更新方法的时候,作为第一个参数的ComponentContext必须总是为更新发生时的生命周期方法中传递进来的那个ComponentContext.这个context包含了现在已知的State的值等重要信息,在布局计算时,这些信息对把旧的Component转换成新的Component非常重要.
  • 在LayoutSpec中,你应该避免在onCreateLayout中调用State更新方法,除非你完全确定它只会发生很少并且确定的的次数。每一次调用State更新方法都会引起Component树的一次新的布局计算,而计算又会反过来调用它其中所有的Component的onCreateLayout方法,因此,这很容易引起死循环。你应该考虑是否使用懒汉式State更新(下面会说到)才是更加适用于你的情况的方法.
  • 在MountSpec中,永远也不要在mount和bind方法中调用State更新方法.如果你需要在这类方法中更新State的值,你应该使用下面会讲到的懒汉式State更新来替代.


Key和识别Component


Litho框架将会为每一个Component设置一个key值,这个值取决于它的类型和它的父级的key值.你可以使用这个key值来确定在State更新的时候哪一个Component才是我们需要更新的,或者使用这个key值在遍历Component树的时候查找Component.

具有相同父级的同类型Component将会被设定相同的key值.因此我们需要一种方法来唯一的识别它们.

此外,当组件的State或者Prop被更新并且Component树重新被创建的时候,会出现一些Component被移除,添加或者在树中重新安排位置的情况.因为Component可能是动态的,因此我们需要一种方法,能在即使Component树改变的情况下还能追踪到应该是哪个Component去进行状态更新.

Component.Builder类会暴露一个.key()方法,你可以使用它来在创建Component的时候制定一个唯一的key给它,以便将来能够识别它.

当你在一个父控件下拥有多个相同类型的Component时,或者你期望的布局内容是动态的时候,你就应该设置这个key值.

一种最常见的你需要手动给你的Component设置key的情况是,你在一个循环中创建和添加子Component:

1
2
3
4
5
6
7
8
9
10
11
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@State boolean isChecked) {
final ComponentLayout.Builder parent = Column.create(c);
for (int i = 0; i < 10; i++) {
parent.child(Text.create(c).key("key" +i));
}
return parent.build();
}


懒汉式State更新


当你想要更新State的值但是又不想立刻触发一次性的布局计算的时候,你可以使用懒汉式State更新.当你调用懒汉式State更新之后,Component将会保持现有的State值直到下一次布局计算被别的机制(例如收到一个新的prop或者定期的State更新)触发,此时State的值才会被更新.在不需要立刻进行布局计算的情况下,懒汉式State更新对想要更新内部Component信息并且在Component树的重新布局中保持这些信息是非常实用的.

为了使用懒汉式State更新,你需要在@State注释中把canUpdateLazily参数设置为true.

如果我们把一个名叫foo的State标记为canUpdateLazily,Litho框架将会自动生成一个静态State更新方法,名叫LazyUpdateFoo,它将带有一个参数,用于设置foo的新值.

State被标记为canUpdateLazily后依然可以使用常规的State更新.

让我们看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@OnCreateLayout
static ComponentLayout onCreateLayout(
final ComponentContext c,
@State(canUpdateLazily = true) String foo) {
FooComponent.lazyUpdateFoo(context, "updated foo");
return Column.create(c)
.child(
Text.create(c)
.text(foo))
.build();
}
@OnCreateInitialState
static void onCreateInitialState(StateValue<String> foo) {
foo.set("first foo");
}

第一次FooComponent被渲染的时候,即使它的State foo已经被懒汉式更新成另一个值了,它的子Text Component也会显示”first foo”.当一个常规的State更新被触发,或者收到一个新的prop的时候,就会触发布局计算,懒汉式更新将会生效,Text将会被渲染成”update foo”.


不可变性


由于后台布局机制,State可以在任何时候被多个线程访问.为了确保线程安全,State对象应该是不可变的(即使有极少数情况下不能实现这一点,那么也应该确保State对象是线程安全的).最简单的解决方案是基于原语来表述你的State,因为根据定义,原语就是不可变的.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-总览和导航

欢迎转载,转载请标明出处.

今天逛github发现facebook开源了一个新框架Litho,大概看了一下介绍,这是一个声明式UI框架,最初是用于为RecyclerView生成复杂的可滚动的UI而做的.它的特点是可以在后台线程中计算布局结构,并且支持资源回收再利用.使用Recycler的思想,自己重新写了一套UI布局框架,可以大大的改进现有的RecyclerView的渲染效率.
果然是facebook,听起来很牛逼啊,在网上搜了一下,暂时还没有中文的文档什么的,所以为了提升英语水平我也来边学边翻译吧,如果翻译有问题的地方,欢迎指出.

英文原文文档地址: Litho-doc
github地址: Litho-Github

Litho 官方文档翻译

介绍Litho


Litho是什么?
编写动机
使用

快速开始


准备工作
教程
编写Component
使用Component

参考


Layout Specs
Mount Specs
Props
State
Layout
Recycler

处理事件


概述
触摸事件处理
可见新的处理

兼容性


Style
无障碍环境
RTL

测试


单元测试

进阶指引


自定义布局
TreeProp
增量式挂载
创建ComponentTree

架构


代码生成器
异步布局
增量式挂载
View的扁平化
回收机制

最佳实践


最佳实践

工具


调试
开发者选项

参与


如何参与
仓库架构