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

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

参考

Props


Litho使用不可变的输入作为单向数据流提供给Component.Component使用React中提出的名称props,作为它的输入.


定义和使用props


一个给定component中的prop是指在你的spec方法中,所有使用@Prop注释标注的参数的集合.你可以把它作为@Prop的参数声明出来,以在所有的方法中访问它的值.

在多个lifecycle方法中,我们可以定义和访问相同的prop.注释处理器会确保你在所有的spec方法中使用一致的prop类型和注释参数.

以下面的Component Spec为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@MountSpec
class MyComponentSpec {
@OnPrepare
static void onPrepare(
ComponentContext c,
@Prop(optional = true) String prop1) {
...
}
@OnMount
static SomeDrawable onMount(
ComponentContext c,
SomeDrawable convertDrawable,
@Prop(optional = true) String prop1,
@Prop int prop2) {
if (prop1 != null) {
...
}
}
}

MyComponentSpec定义了两个Prop,一个String类型的Prop,名叫prop1,和一个int类型的prop,名叫prop2.prop1是可选的,它需要在所有定义它的方法中进行标注,否则注释处理器将会抛出一个异常.

当生命周期方法被调用时,@Prop参数将保存component创建时从它们的父级传递过来的值(或者它们的默认值).

Prop在LayoutSpec和MountSpec中定义和使用的方法都是一样的.


设置Prop


对每一个在spec中定义的独立的prop,注释处理器都会创建一个与prop名称相同的构造器模式的方法在Component的Builder中,用来把传入的参数设置到对应的prop上.

你可以调用自动生成的Component Builder中的对应的方法传入参数来设置prop.

1
2
3
4
MyComponent.create(c)
.prop1("My prop 1")
.prop2(256)
.build();


Prop的默认值


对可选的prop你可以省略设置它的value的动作,它会被自动设置为它这种类型在java中的默认值.你可能也会经常需要明确的定义component prop的默认值为你想要的值,而不是简单的使用java的默认值.

你可以使用@PropDefault注释在Spec类中定义一个静态的成员变量作为prop的默认值.让我们为上面例子中prop定义一个默认值:

1
2
3
4
5
6
7
@MountSpec
public class MyComponentSpec {
@PropDefault static final String prop1 = "mydefaultvalue";
@PropDefault static final int prop2 = -1;
...
}


资源类型


当创建布局的时候,经常会使用到Android资源系统中的值,例如dimension,color,string等等.Component框架提供了一个非常方便的方法使用注释来设置Android资源系统里的值到prop中.

让我们看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
@LayoutSpec
public class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
LayoutContext context,
@Prop CharSequence someString,
@Prop int someSize,
@Prop int someColor) {
...
}
}

在上面的例子中,MyComponent拥有几个prop,它们预计会被设置为一个代表color的integer(someColor),一个代表像素的dimension(someSize)和一个String(someString).通常的,你需要使用资源的值来设置它们:

1
2
3
4
5
6
Resources res = context.getResources();
MyComponent.create(c)
.someString(res.getString(R.string.my_string))
.someSize(res.getDimensionPixelSize(R.dimen.my_dimen))
.someColor(res.getColor(R.color.my_color))

而component 框架允许你使用资源类型来注释你的prop,以便你的component builder可以生成更加方便的方法来供你直接使用资源的值.

1
2
3
4
5
6
7
8
9
10
11
12
@LayoutSpec
public class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
LayoutContext context,
@Prop(resType = ResType.STRING) CharSequence someString,
@Prop(resType = ResType.DIMEN_SIZE) int someSize,
@Prop(resType = ResType.COLOR) int someColor) {
...
}
}

当你做了以上的更改后,MyComponent的builder将会根据被注释的prop的资源类型自动包含响应的Res,Attr,Dip,Px方法.因此你就可以像下面这么做:

1
2
3
4
5
MyComponent.create(c)
.someStringRes(R.string.my_string)
.someSizePx(10)
.someSizeDip(10)
.someColorAttr(android.R.attr.textColorTertiary)

其他支持的资源类型包括 ResType.STRING_ARRAY, ResType.INT, ResType.INT_ARRAY, ResType.BOOL, ResType.COLOR, ResType.DIMEN_OFFSET, ResType.FLOAT, and ResType.DRAWABLE.


不可变性


Component中的prop是只读的.当Component的父级创建Component时传递给它的prop响应的值之后,在component的生命周期中,它们的值就不能被改变了.如果prop的值必须被更新,那么它的父级创建一个新的Component并且传递将新的值传递给它的prop.prop对象必须保证是不可变的.因为在后台布局计算中prop可能被多个线程同时访问.Prop的不可变性能够确保在你的component层级中不会出现线程安全问题.




回到导航页




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

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

参考

Mount Specs


一个Mount Spec定义了一个可以渲染Views或者drawables的component.

Mount Spec只有在你需要把自己的view/drawable集成到Component 框架中的时候才应当被创建.这里的Mount的意思是指布局树中所有的component执行的操作,用于提取它们的渲染状态(一个View或者一个Drawable)以供显示.

Mount spec类应该使用@MountSpec去注释,并且至少实现一个@OnCreateMountContent方法.下方其他列出的方法是可选择实现的.

mount spec component的生命周期如下:

  • 在布局计算之前,运行@OnPrepare一次
  • 在布局计算过程中,可选择的运行OnMeasure.
  • 在布局计算之后,运行@OnBoundsDefined一次.
  • 在component添加到托管视图之前,运行@OnCreateMountContent
  • 在component添加到托管视图之前,运行@OnMount
  • 在component添加到托管视图之后,运行@OnBind
  • 在从托管视图移除component之前,运行@OnUnBind
  • 在从托管视图移除component之前,可选择的运行@OnUnmount


挂载


让我们从一个简单的ColorComponent开始,它有一个prop表示颜色名,并且装载它自己的ColorDrawable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@MountSpec
public class ColorComponentSpec {
@OnCreateMountContent
static ColorDrawable onCreateMountContent(ComponentContext c) {
return new ColorDrawable();
}
@OnMount
static void onMount(
ComponentContext context,
ColorDrawable colorDrawable,
@Prop String colorName) {
colorDrawable.setColor(Color.parseColor(colorName));
}
}

  • 挂载操作的API与Android的RecyclerView Adapter非常相似.它有一个onCreateMountContent方法在回收池为空的时候创建和初始化View/Drawable内容,以及一个onMount方法能够根据当前的信息更新回收内容.
  • onCreateMountContent的返回类型应该始终和onMount的第二个参数的类型相一致。它必须为View或Drawable的子类。这在编译时由注释处理器去验证。
  • 挂载总是发生在主线程中因为它可能需要处理Android Views(它们被绑定在主线程中).
  • onCreateMountContent不能使用@Prop或任何其他带注释的参数。
  • 鉴于@OnMount方法始终在UI线程中运行,因此不应执行耗时的操作。


阶段之间的输入和输出


你可以通过把重操作(耗时操作)移动到@OnPrepare方法中,来减轻UI线程的压力.这个方法只会在布局计算前执行一次,并且可以在后台线程中执行.

假设现在我们需要将在UI线程之外解析得到的颜色名称应用到ColorComponent中.为了做到这一点,我们需要一种将@OnPrepare方法中生成的值传递给@OnMount方法的途径.Component框架提供了阶段间的输入和输出,使你能够做到这一点.

让我们看看ColorComponent的@OnPrepare方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@MountSpec
public class ColorComponentSpec {
@OnPrepare
static void onPrepare(
Context context,
@Prop String colorName,
Output<Integer> color) {
color.set(Color.parseColor(colorName));
}
@OnCreateMountContent
static ColorDrawable onCreateMountContent(ComponentContext c) {
return new ColorDrawable();
}
@OnMount
static void onMount(
ComponentContext context,
ColorDrawable colorDrawable,
@FromPrepare int color) {
convertDrawable.setColor(color);
}
}

在@MountSpec方法中使用Output<?>会自动的创建一个输入在下一个阶段中.在这种情况下,一个@OnPrepare的输出就会在@OnMount中创建一个输入.

在编译期间,注释处理器将会确保阶段间的不变性,例如你不能在@OnPrepare中使用@OnMeasure的输出,因为@OnPrepare总是在@OnMeasure之前执行.


测量


如果你需要在布局计算阶段定义如何测量你的component,那么你就需要实现@OnMeasure方法.

现在,让我们假设我们需要我们的ColorComponent有一个默认的宽度,并且当它的高度未定义的时候,能够强制执行一定的宽高比.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@OnMeasure
static void onMeasure(
ComponentContext context,
ComponentLayout layout,
int widthSpec,
int heightSpec,
Size size) {
// If width is undefined, set default size.
if (SizeSpec.getMode(widthSpec) == SizeSpec.UNSPECIFIED) {
size.width = 40;
} else {
size.width = SizeSpec.getSize(widthSpec);
}
// If height is undefined, use 1.5 aspect ratio.
if (SizeSpec.getMode(heightSpec) == SizeSpec.UNSPECIFIED) {
size.height = width * 1.5;
} else {
size.height = SizeSpec.getSize(heightSpec);
}
}

在@OnMeasure方法中,你可以像以前一样使用@Prop注释访问Component props.SizeSpec的API类似于Android中的MeasureSpec.

就像@OnPrepare一样,@OnMeasure方法也能生成阶段间的输出(能够使用@FromMeasure注释的参数来访问),并且可以在后台线程中执行.


ShouldUpdate


Mount Spec可以使用@ShouldUpdate注释定义一个方法来避免在更新时进行重新测试和重新挂载。
@ShouldUpdate的调用的前提是component是”纯渲染函数”。一个组件如果是纯渲染函数,那么它的渲染结果只取决于它的prop和状态.这意味着在@OnMount期间,组件不应该访问任何可变的全局变量。
一个@MountSpec可以通过使用@MountSpec注释的pureRender参数来定自己为”纯渲染的”。只有纯渲染的Component可以假设当prop不更改时就不需要重新挂载。@ShouldUpdate函数可以定义如下:

1
2
3
4
@ShouldUpdate(onMount = true)
public boolean shouldUpdate(Diff<String> someStringProp) {
return !someStringProp.getPrevious().equals(someStringProp.getNext());
}

shouldUpdate中的参数是prop或状态的对比差异。Diff是一个包含旧Component层级结构中@Prop或@State的值以及新Component层级结构中相同的@Prop或@State值的类。在这个示例中,我们将someStringProp定义为一个String类型的@Prop。shouldUpdate方法将收到一个Diff,以便能够比较此@Prop的旧值和新值。
shouldUpdate必须考虑在@OnMount时使用的任何prop和状态。它可以安全地忽略仅在“@OnMount/@OnUnbind”时间使用的prop和状态,因为这两个方法无论如何都会被执行。

@ShouldUpdate注释上的onMount属性可以控制是否在挂载时进行shouldUpdate检查。默认情况下,Litho将尝试在layout的时候执行检查,但是在检查布局差异功能被关闭的时候,作为替代方案,将onMount设置为true,在挂载时执行此检查就变得很有用了。默认情况下,onMount属性被设置为false,因为相等检查本身可能很耗时,这会使挂载性能变得更差。

@ShouldUpdate注释方法目前仅支持在@MountSpec中使用。我们计划在未来在更复杂的布局中也支持它,但目前在@LayoutSpec中用@ShouldUpdate注释的方法将不起作用。




回到导航页




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

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

参考

Layout Specs


Layout Spec在逻辑上等同于Android的View的组合.它简单的把一些已经存在的component组合到一个不可变的布局树中.

实现一个layout spec非常简单:你只需要写一个标注为@OnCreateLayout的方法,并且让它返回一个不可变的ComponentLayout对象的树.

让我们从一个简单的例子开始:

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
@LayoutSpec
public class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop int color,
@Prop String title) {
return Row.create(c)
.alignItems(CENTER)
.child(
SolidColor.create(c)
.colorRes(color)
.withLayout()
.widthDip(40)
.heightDip(40))
.child(
Text.create(c)
.text(title)
.textSizeRes(R.dimen.my_text_size)
.withLayout()
.flexGrow(1f))
.build();
}
}

正如你所见,layout spec类使用@LayoutSpec注释.

用@OnCreateLayout注释标注的方法必须以ComponentContext作为第一个参数,并且在它后面有以@Prop标注的其他参数.注释处理器将会在编译的时候对它们和其他API的不变性进行验证.

在上面的示例中,布局树中有一个根容器,容器中有两个水平堆叠(Row.create)且垂直居中(Align.CENTER)的子节点.

第一个子节点是一个SolidColor component,它拥有一个colorRes的Prop和40dp的宽和高.

1
2
3
4
5
SolidColor.create(c)
.uri(imageUri)
.withLayout()
.width(40)
.height(40)

第二个子节点是一个Text Component,它拥有一个名叫text的prop,并且使用grow(1f)填充了Myconponent中的剩余的水平空间(等同于Android LinearLayout中的layoutWeight).文字大小实在my_text_size尺寸文件中定义的.

1
2
3
4
5
Text.create(c)
.text(title)
.textSizeRes(R.dimen.my_text_size)
.withLayout()
.grow(1f)

你可以查看完整的Yoga文档来获取所有框架开放出的布局特性.




回到导航页




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

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

快速开始

编写Component


Component Specs


一个Component Spec可以生成一个你在UI中使用的Component.有两种类型的Component Spec:

  • Layout spec: 可以结合其他component至一个特定的布局中.类似于Android中的ViewGroup.
  • Mount spec: 一个可以渲染view或者drawable的的组件.

现在,我们来看一下布局spec的整体结构:

1
2
3
4
5
6
7
8
9
10
11
@LayoutSpec
class MyComponentSpec {
@OnCreateLayout
static ComponentLayout onCreateLayout(
ComponentContext c,
@Prop String title,
@Prop Uri imageUri) {
...
}
}

有几件事需要注意:

  • Component Spec只是有着特殊注释的普通java类.
  • Component Spec是完全无状态的,并且不包含任何的成员变量.
  • 带有@Prop注释的参数将会自动的添加到Component构造器中.
  • 为了能从Component Spec自动生成Component,你需要添加Litho注释处理器至你的BUCK或者Gradle文件中.请参阅入门指南,了解如何做到这一点.你可以通过向类注释添加isPublic=false来使生成的类变为private的.


Spec,生命周期和Component类


一个Component Spec子类将被处理用于生成一个ComponentLifecycle的子类,这个子类的名字将会是Spec的名字去掉soec后缀.例如,MyComponentSpec将会生成MyComponent类.

这个生成的ComponentLifeCycle类就是今后你会在你的产品中使用的类.而Spec类将在运行的时候在新生成的代码里被用作一个代表类.

生成的新类暴露出来的唯一的API是一个create(…)方法,它为您在Spec类中声明的@Props返回相应的Component.Builder。

在运行的时候,同一种类型的component的所有实例都共享相同的componentLifecycle引用.这意味着一个spec实例对应的是一种component类型,而不是一个component实例.




回到导航页




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

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

快速开始

教程


本教程已经假定你已经按照准备工作正确设置了Litho.

在本教程中,你将首先使用Litho在屏幕上构建一个基本的”Hello World!”,然后再构建一个”Hello World!” item组成的列表。在这个过程中,你将了解到Litho的构建块:ComponentLithoView。你将学习如何设置Component的属性。


1.Hello World


在最初的步骤中,你将显示一个带有”Hello World”的View.

首先,在Application中初始化SoLoader.

1
2
3
4
5
6
7
8
9
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
}
}

Litho在后台使用Yoga加载布局.Yoga需要依赖本地库,而我们引入SoLoader来处理加载这些本地库的工作.在此处初始化SoLoader确保你稍后不会引用到那些未加载的库.

另外,如果你想要调试你的Component层级结构,你可以按照其中的步骤安装Stetho.

下一步,添加一个Litho中预定义好的Text Component到activity中:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ComponentContext context = new ComponentContext(this);
final Component component = Text.create(context)
.text("Hello World")
.textSizeDip(50)
.build();
setContentView(LithoView.create(context, component));
}

LithoView是一个可以渲染Component的Android ViewGroup.它是连接Android view和Litho Component的桥梁.上面的例子就把一个展示Text Component的LithoView设置到了activity中的content中.

那么component如何发挥作用呢?让我们看一下这段代码:

1
2
3
4
Text.create(context)
.text("Hello World")
.textSizeDip(50)
.build();

Text是在com.facebook.litho.widget中定义的核心组件.如你所见,它有诸如text和textSize这样的一系列的属性.我们从React中获得灵感,这些属性我们称之为props.

稍后,你将学习到如何编写自己的component,但是值得注意的是,Text类是由TextSpec类生成的,生成的component类提供了一套API方法来设置props的值.

在示例中,这个Text Component被作为一个单独的子控件添加至LithoView中.你也可以用一个根Component包含许多子Component来替代示例中的做法.在后续的例子中,你将会学习如何这么做.

完成了!让我们运行app,你可以在屏幕上看到这样的显示.

虽然并不漂亮,但是这已经是一个好的开端了!


2.你的第一个自定义Component


在本教程的末尾,你将会拥有一个可以滚动的列表,这个列表将循环的显示一个包含标题和副标题的item.简直激动人心!

在教程的本部分中,你将编写一个简单的component作为列表的item.当然,现实世界中的app的item会更加复杂,但是你会在这个示例中学到你今后需要所有基础的知识.

准备好了吗?是时候深入探索并构建该Component了。在Litho中,你可以编写Spec类来声明Component的布局。框架随后会生成底层的Component类供您在代码中创建实例。

您的自定义component将被称为ListItem,它将包含一个标题,和一个在其下方的稍小的副标题.因此,你需要创建一个包含以下内容的ListItemSpec类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@LayoutSpec
public class ListItemSpec {
@OnCreateLayout
static ComponentLayout 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 Component.在这个例子中,你要将其作为一个子属性添加至一个Column中.你可以把Column等同于HTML中的

标签.它是一个包装器,用于把组件整合在一起,并且可能会添加一些背景样式.由于Litho使用Yoga,你可以添加flexbox属性来设置Column或Row的子项的布局.在此处,你只需简单设置padding(填充大小)和背景颜色.

你如何渲染这个Component呢?在你的activity中,简单的修改Component定义为:

1
final Component text = ListItem.create(context).build();

注意:你使用的是ListItem,而不是ListItemSpec.

这个ListItem是哪里来的呢?create方法和build方法是在哪里定义的呢?这是Litho Specs的魔力所在.

准备工作中,我们学习了如何添加依赖至项目中,来使代码生成器能够工作.这会在你的代码上运行一个注释处理器.它会自动查找FooSpec的类名,并且自动生成根据Spec类在同一个包下生成Foo类.Litho将会为这些类自动添加所需的所有方法.此外,根据规则,注释处理器还将生成的额外方法(例如Text的textSizeSp方法或者Column/Row的backgroundColor方法).

就这么简单。运行你的app,你应该看到如下的画面:


3.创建一个列表


你可以使用Litho的核心组件Recycler Component来处理列表相关的工作.这个component在概念上类似于Android的RecyclerView,然而,使用Litho,所有的布局计算都是在一个子线程中处理的,这带来了显著的性能提升.在教程的本部分中,你将使用一个RecyclerBinder来为Recycler提供Component,方式与使用LayoutManager与Adapter配合向RecyclerView提供View的方式相同.
首先,在你的activity中,像下面这样定义Component:

1
2
3
4
5
6
7
final RecyclerBinder recyclerBinder = new RecyclerBinder(
context,
new LinearLayoutInfo(this, OrientationHelper.VERTICAL, false));
final Component component = Recycler.create(context)
.binder(recyclerBinder)
.build();

这些代码构造了一个RecyclerBinder并且连接它到了一个Recycler上.新的RecyclerBinder使用context和layoutInfo作为构造参数.

然后再创建Recycler并且把它传递给LithoView.

现在,将重点放在使用item填充binder上.让我们定义一个helper方法来做这件事:

1
2
3
4
5
6
7
8
9
private static void addContent(RecyclerBinder recyclerBinder, ComponentContext context) {
for (int i = 0; i < 32; i++) {
recyclerBinder.insertItemAt(
i,
ComponentInfo.create()
.component(ListItem.create(context).build())
.build());
}
}

在代码中,我们需要创建一个ComponentInfo来描述Recycler需要展示的component.在本例中,我们需要展示ListItem.

最后,在组件定义工作完成后,在activity的onCreate回调中调用addContent方法.

1
addContent(recyclerBinder, context);

运行app,你会看见一个可以滚动的具有32个ListItem的列表.


4.定义Component的属性


列表如果只是简单的包含同一个component的拷贝就没有意义了.在这个部分,你需要把目光集中到属性,或者说props上.你可以设置很多的属性到Component上来改变它的外观和行为.

为一个component添加props非常的简单.props是componentSpec的方法的参数,使用@Prop定义.

像下面这样修改ListItemSpec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@OnCreateLayout
static ComponentLayout 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();
}

这样就添加了3个props:title,subtitle和color.注意现在背景颜色和Text的文字内容不再是写死的了,而是取决于onCreateLayout方法的参数了.

神奇的事就发生在@Prop和注释处理器中,处理器以正确的方法生成符合props的component构造器.你现在可以修改你的binder构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void addContent(
RecyclerBinder recyclerBinder,
ComponentContext context) {
for (int i = 0; i < 32; i++) {
ComponentInfo.Builder componentInfoBuilder = ComponentInfo.create();
componentInfoBuilder.component(
ListItem.create(context)
.color(i % 2 == 0 ? Color.WHITE : Color.LTGRAY)
.title("Hello, world!")
.subtitle("Litho tutorial")
.build());
recyclerBinder.insertItemAt(i, componentInfoBuilder.build());
}
}

现在,当ListItem被构建出来时,color,title和subtitle 这些props就被传递进去了来改变每一行的背景颜色.

运行app,你可以看到如下画面:

你可以为@Prop注释指定更多的选项.例如下面的属性:

1
@Prop(optional = true, resType = ResType.DIMEN_OFFSET) int shadowRadius,

它告诉注释处理器构造一些函数,如shadowRadiusPx,shadowRadiusDip,shadowRadiusSp以及shadowRadiusRes。

恭喜完成本教程!这个基础教程向你介绍了开始使用Litho所需要的所有基础构建块,并且教你构建了自己的Component。你可以在com.facebook.litho.widgets包中找到可以使用的预定义好的组件Component。你可以在这里找到完整的教程。请务必查看此示例和Litho API文档以获取更深入的代码。




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-准备工作

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

快速开始

准备工作


集成Litho至你的工程



gradle


你可以通过在你的Gradle的build.gradle文件中添加以下代码来把Litho添加至你的Android工程中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dependencies {
// ...
// Litho
compile 'com.facebook.litho:litho-core:0.2.0'
compile 'com.facebook.litho:litho-widget:0.2.0'
provided 'com.facebook.litho:litho-annotations:0.2.0'
annotationProcessor 'com.facebook.litho:litho-processor:0.2.0'
// SoLoader
compile 'com.facebook.soloader:soloader:0.2.0'
// 调试选项
debugCompile 'com.facebook.litho:litho-stetho:0.2.0'
// 集成Fresco的支持
compile 'com.facebook.litho:litho-fresco:0.2.0'
// 供测试
testCompile 'com.facebook.litho:litho-testing:0.2.0'
}


Buck


你可以通过在你的Buck的BUCK文件中添加以下代码来把Litho添加至你的Android工程中.

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
android_prebuilt_aar(
name = "litho",
aar = ":litho.aar",
visibility = ["PUBLIC"],
)
remote_file(
name = "litho-core.aar",
sha1 = "sha1here",
url = "mvn:com.facebook.litho:litho-core:aar:0.2.0",
)
prebuilt_jar(
name = "litho-annotation",
binary_jar = ":litho-annotation.jar",
visibility = ["PUBLIC"],
)
remote_file(
name = "litho-processor.aar",
sha1 = "sha1here",
url = "mvn:com.facebook.litho:litho-processor:aar:0.2.0",
)
prebuilt_jar(
name = "litho-processor",
binary_jar = ":litho-processor.jar",
visibility = ["PUBLIC"],
)
remote_file(
name = "litho-annotation.jar",
sha1 = "sha1here",
url = "mvn:com.facebook.litho:litho-annotation:jar:0.2.0",
)
android_prebuilt_aar(
name = "litho-widget",
aar = ":litho-widget.aar",
visibility = ["PUBLIC"],
)
remote_file(
name = "litho-widget.aar",
sha1 = "sha1here",
url = "mvn:com.facebook.litho:litho-widget:aar:0.2.0",
)
litho_android_library(
...
# Your target here
...
annotation_processor_deps = [
":litho-annotation",
":litho-processor",
],
annotation_processors = [
"com.facebook.litho.processor.ComponentsProcessor",
],
deps = [
":litho",
":litho-widget",
...
]
)


测试集成情况


你可以通过添加一个由Litho创建的view至activity来测试集成情况

首先,初始化SoLoader.Litho依赖SoLoader来加载底层布局引擎Yoga所需的本地库.在Application中进行这个操作比较合适.

1
2
3
4
5
6
7
8
9
10
[MyApplication.java]
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
}
}

然后,添加一个预定义的Litho Text组件至activity来显示”Hello world”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[MyActivity.java]
import com.facebook.litho.ComponentContext;
import com.facebook.litho.LithoView;
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ComponentContext c = new ComponentContext(this);
final LithoView lithoView = LithoView.create(
this /* context */,
Text.create(c)
.text("Hello, World!")
.textSizeDip(50)
.build());
setContentView(lithoView);
}
}

现在,当你运行app的时候,你就可以看到”Hello World”显示在屏幕上.




回到导航页




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

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

快速开始

使用Component


生成的Component类为你在spec中定义的props提供了一个简单的构造器.为了在你的UI中使用生成的Component,你需要一个LithoView,它是一个Android ViewGroup并且可以渲染component.

你可以通过以下的代码制定一个LithoView来渲染一个Component.

1
2
3
4
5
final Component component = MyComponent.create()
.title("My title")
.imageUri(Uri.parse("http://example.com/myimage")
.build();
LithoView view = LithoView.create(context, component);

在这个例子中,MyComponent将被托管给LithoView,你可以在你的程序中像使用一个普通的Android View一样去使用这个LithoView.你可以在教程里看到如何在一个Acitivity中使用这个LithoView.

重要提醒:示例中的LithoView,如果你在你的View层级中直接使用,将会在主线程中同步的执行布局任务.有关在主线程之外执行布局任务的更多信息,请参阅异步布局.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-异步布局

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

架构

异步布局(layout)


不可变性和线程安全


大多数线程安全的问题都是由对可变对象的并发读写造成的.在Java中,一个典型的此类问题是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
public class SomeExampleClass {
private int mCounter;
public String getThisOrThat() {
if (mCounter > 10) {
return "this":
} else {
mCounter++;
return "that";
}
}
}

如果有多个线程都调用一个共享的SomeExampleClass中的的getThisOrThat方法,这就构成了一个最经典的竞争情况.当第二个线程进入这个方法尝试获取mCounter时,第一个线程可能正在执行mCounter++,因此我们不能确定第二个线程从mCounter中读到的值是什么.出现这个问题的原因是在我们的代码中存在一个可变的状态变量(mCounter),并且有多个线程尝试去读写它.竞争情况是我们在使用多线程编程去处理任务时最常遇到的问题.

而这就是为什么传统上在多线程上运行UI代码都会变得极其复杂的原因.Android中的view是有状态的和可变的.比如一个TextView,它必须保持追踪现在显示的文字,并且暴露一个setText()方法给开发者去修改文字.这就意味着Android UI框架如果决定要分流一些工作(例如layout计算)到第二个线程中去做,它就必须解决用户在其他线程中调用setText()而改变了当前正在布局计算中的文字的问题.

让我们回到我们刚才的示例代码.我们说主要的问题就在我们的getThisOrThat()方法中的可变状态变量mCounter的存取.有没有一种方法能够做到功能上和它一致但是却不用依赖这种可变的状态变量呢?让我们想象一种情况:所有对象在创建后都不能改变其自身的内容.如果没有内容改变,我们也就不会有线程间尝试存取同一个状态变量的竞争问题了.我们可以重新改写一下我们的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class Result {
public final int mCounter;
public final String mValue;
public Result(int counter, int value) {
mCounter = counter;
mValue = value;
}
}
public class SomeExampleClass {
public static Result getThisOrThat(int counterValue) {
if (counterValue > 10) {
return new Result(counterValue, "this"):
} else {
return new Result(counterValue + 1, "that");
}
}
}

我们的方法现在完全是线程安全的了,因为它从来没有改动过任何SomeExampleClass中的内部状态变量.在这个例子中,getThisOrThat()方法我们称之为”纯净方法(pure function)”,因为它的输出结果只取决于输入值并且这么做没有任何副作用.

在Litho中我们尝试应用这种概念到布局计算中.Component是一个包含了所有提供给布局方法的输入值的不可变的类.而这些输入值以@Prop和@State的格式提供.这也解释了为什么我们需要@Prop和@State为不可变的.因为如果它们是可变的,我们获取布局的函数就失去了作为”纯净方法”的性质.

在Java中,不可变性在中通常意味着需要花费时间做更多的内存分配动作.即使在我们的简单的示例中,我们每次调用我们的方法时都需要分配一个新的Result对象内存.而Litho使用池的概念和代码生成的概念来自动优化对象内存分配,使得内存分配花费降到最小.


同步和异步操作


Litho同时提供了同步的和异步的API用来做布局计算.两种API都是线程安全的并且都可以在任意线程中调用.最终的布局总是呈现最后使用setRoot()或者setRootAsync()设置的Component.

同步的布局计算能够确保一旦在ComponentTree中调用了setRoot,布局计算的结果就能立刻准备好,以供挂载到LithoView上.

而它的主要的缺陷是由于它的布局计算工作发生在调用setRoot()的线程中,因此不建议在主线程中调用它.而在另一方面,在一些情况下,你不能在展示一些东西在屏幕上之前就等待后台进程去计算布局,比如说你要展示的item已经在视窗口了.这种情况下调用setRoot()是最好的选择.使用同步的操作也能是集成Litho至已存在的线程体系变得更加简单.如果你的程序已经拥有了一个复杂并且结构化的线程设计,你可能会不想依赖Litho的内建线程去完成布局计算.

异步的布局计算将会使用Litho的”布局线程”来计算布局,这意味着当调用布局计算时,布局工作将会立刻进入另一个独立线程的工作队列中而不会立即向它的调用线程返回结果.异步的布局操作被广泛的应用于RecyclerBinder的示例中.




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-View的扁平化

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

架构

View的扁平化


让我们看看下方的例子.它包含一个图片一个标题和一个副标题.在传统的Android View 系统里,你可以使用几个view来包含这些元素,再用一个viewGroup来包裹它们.

Litho会自动减少最终UI层级结构所包含的View数量。布局计算这一步产生的布局树只是你的UI的蓝图,与Android的View没有直接的耦合。这允许框架在挂载组件之前处理布局树以获得最佳渲染性能。

我们使用两种途径来实现:
首先,Litho能在布局计算后完全忽略容器类,因为它们在挂载步骤中不会被用到。拿我们的例子来说,在挂载时,不会有单独包含标题和子标题的View。
第二,Litho可以选择挂载一个view或者挂载一个drawable。事实上,对于Litho框架里的大多数核心组件,如Text和Image,挂载的是drawable,而不是view。

这些优化的结果是,示例中UI的组件实际上将被渲染为一个独立,扁平的视图。你可以在下面的屏幕截图中看到这一点(启用了开发者选项中的显示布局边界).

虽然扁平化的view对于减少内存使用和缩短绘制时间有相当大的好处,但它并不是一颗银弹(译者注:即它不是一个对所有情况都能用的万用方案)。当我们希望依赖Android view的特定功能时(例如触摸事件处理,可访问性或局部无效化),Litho提供一个非常通用的系统来自动对挂载Component的视图层次结构”去扁平化”。例如,如果要在示例中启用单击图像或文本,如果设置了点击处理器,框架将自动把文字或图像包装在view一个新的view中。




回到导航页




Facebook出品的Android声明式开源新框架Litho文档翻译-代码生成器

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

架构

代码生成器


正如在编写Conponent中写道的,Litho依赖代码生成器来从ComponentSpec生成Component.这个过程需要用到一个不可变的java对象——SpecModel,作为中间件。

代码生成过程分为三个步骤:

  • 从ComponentSpec中生成一个SpecModel。
  • 验证SpecModel的合法性
  • 从SpecModel中生成Component。


创建SpecModel


SpecModel是在编译时,由注解处理器(一个扫描和处理注解的javac工具)生成的.Litho的注解处理器将会扫描你的ComponentSpec中的方法,变量和注释,为每一个ComponentSpec创建SpecModel.
未来,我们将会支持使用其他方法创建SpecModel.举个例子:我们考虑支持直接在Android Studio/Intellij中创建SpecModel,这样可以允许我们不用build源码就可以生成Component.


SpecModel验证


SpecModel有一个方法叫做validate(),它返回一个包含SpecModelValidationError(SpecModel验证错误)的list.如果这个list为空,说明这个Spec是格式合法的,可以用它来创建一个合法的component.如果不是,这个list则会包含一系列需要在Component生成前修复的错误.


Component生成


如果SpecModel验证步骤成功了,接着generate方法将会被调用,它将会生成一个Javapoet TypeSpec(用它能够很容易的创建一个Component的class 文件).


给你的工程设置代码生成器


如果你根据准备工作中的说明设置了你的工程代码,那么代码生成器就被自动设置好了。




回到导航页