jetpack tx2_了解jetpack构成第1部分,共2部分

Jetpack Compose是一个现代化的UI工具包,旨在解决快速高效创建优美用户界面的技术挑战。它强调关注点分离,通过声明式编程降低耦合,提高内聚性。Compose允许开发者使用Kotlin定义UI,简化布局和视图模型的交互,避免XML布局和编程语言之间的语言差异。此外,Compose支持可组合函数,实现声明式UI,允许动态更新,以及通过封装和重新组合优化代码结构。通过这种方式,Compose提升了Android应用的可维护性和扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jetpack tx2

重点 (Top highlight)

The expectations around UI development have grown. Today, we can’t build an app and meet the user’s needs without having a polished user interface including animation and motion. These requirements are things that didn’t exist when the current UI Toolkit was created. To address the technical challenges of creating a polished UI quickly and efficiently we have introduced Jetpack Compose, a modern UI toolkit that sets app developers up for success in this new landscape.

对UI开发的期望已经增长。 今天,如果没有完善的用户界面(包括动画和运动),我们将无法构建应用程序并满足用户的需求。 这些要求是创建当前UI Toolkit时所不存在的。 为了解决快速有效地创建优美的UI的技术难题,我们引入了Jetpack Compose,这是一个现代UI工具包,可让应用程序开发人员在新的形势下取得成功。

Over two posts we’re going to explain the benefits of Compose and look at how it works under the hood. To start, in this post, I discuss the challenges Compose addresses, the reasons behind some of our design decisions, and how those help app developers. Also, I will discuss the mental model of Compose, how you should think about the code you write in Compose, and how you should shape your APIs.

在两篇文章中,我们将解释Compose的好处,并了解它的幕后工作原理。 首先,在本文中,我将讨论Compose所面临的挑战,一些设计决策背后的原因以及这些决策如何帮助应用程序开发人员。 此外,我还将讨论Compose的思维模型,如何考虑在Compose中编写的代码以及如何调整API。

Compose解决了哪些挑战? (What challenges does Compose address?)

Separation of concerns is a well-known software design principle. It’s one of the fundamental things that we learn as app developers. Despite being well known, it is often difficult to grasp whether or not this principle is being followed in practice. It can be helpful to think of this principle in terms of “Coupling” and “Cohesion”.

关注点分离是众所周知的软件设计原则。 这是我们作为应用程序开发人员学习的基本知识之一。 尽管众所周知,但是通常很难掌握实践中是否遵循该原理。 从“耦合”和“内聚性”的角度考虑该原理可能会有所帮助。

When we write code, we create modules that consist of multiple units. Coupling is the dependency among units in different modules and reflects the ways in which parts of one module influence parts of other modules. Cohesion is instead the relationship between the units within a module, and indicates how well grouped the units in the module are.

编写代码时,我们将创建包含多个单元的模块。 耦合 是不同模块中各个单元之间的依存关系,反映了一个模块的各个部分影响其他模块的各个部分的方式。 凝聚 而是模块中各单元之间的关系,并指示模块中各单元的分组程度。

When writing maintainable software, our goal is to minimize coupling and maximize cohesion.

在编写可维护的软件时,我们的目标是最大程度地减少耦合最大化内聚力

Image for post

When we have highly coupled modules, making a change to code in one place means having to make many other changes to other modules. Worse still, coupling can often be implicit so that unexpected things break because of a change that appears to be entirely unrelated.

当我们拥有高度耦合的模块时,在一处更改代码意味着必须对其他模块进行许多其他更改。 更糟糕的是,耦合通常可能是隐式的,以至于意外事件由于似乎完全无关的更改而中断。

Separation of concerns is about grouping as much related code together as possible so that our code can be easily maintained and scale as the app grows.

关注点分离是将尽可能多的相关代码分组在一起,以便我们的代码可以轻松维护并随着应用程序的增长而扩展。

Let’s look at this more practically in the context of Android development today and take the example of a view model and an XML layout.

让我们在当今的Android开发环境中更实际地进行研究,并以视图模型和XML布局为例。

Image for post

The view model provides data to the layout. It turns out there can be a lot of dependencies hidden here: a lot of coupling between the view model and the layout. One of the more familiar ways that you can see this manifest is through APIs that require some amount of knowledge of the shape and content of the XML layout itself, such as findViewByID.

视图模型为布局提供数据。 事实证明,这里隐藏了很多依赖关系:视图模型和布局之间存在很多耦合。 可以看到此清单的一种较为熟悉的方式是通过API,这些API需要对XML布局本身的形状和内容有一定的了解,例如findViewByID

Using these APIs requires knowledge of how the XML layout is defined and creates a coupling between the two. As our app grows over time we have to ensure that none of these dependencies become outdated.

使用这些API需要了解如何定义XML布局并在两者之间建立耦合。 随着我们的应用程序随着时间的增长,我们必须确保所有依赖项都不会过时。

Most modern apps display UI dynamically and evolve during their execution. As a result, one needs to not only verify that these dependencies are satisfied by the layout XML statically, but that they will be satisfied for the life of the program as well. If an element leaves the view hierarchy at runtime, some of these dependencies may be broken and can lead to issues like NulReferenceExceptions.

大多数现代应用动态显示UI,并在执行过程中不断发展。 结果,不仅需要验证布局XML静态地满足了这些依赖关系,而且还需要在程序的整个生命周期中满足这些依赖关系。 如果元素在运行时离开视图层次结构,则其中的某些依赖项可能会被破坏,并可能导致诸如NulReferenceExceptions问题。

Image for post

Typically the view model is defined in a programming language such as Kotlin and the layout in XML. Because of this difference in language, there’s a forced line of separation, even though the view model and the layout XML can sometimes be intimately related. In other words, they’re very tightly coupled.

通常,视图模型是用诸如Kotlin之类的编程语言定义的,而布局则是用XML定义的。 由于语言上的这种差异,即使视图模型和布局XML有时可能紧密相关,也存在强制的分隔线。 换句话说,它们紧密耦合。

This begs the question: What if we started to define the layout, the structure of our UI, in the same language? What if we chose Kotlin?

这就引出了一个问题:如果我们开始用相同的语言定义布局,UI的结构该怎么办? 如果我们选择Kotlin怎么办?

Image for post

Because we would then be working in the same language, some of the dependencies that were previously implicit might start to become more explicit. We can also refactor the code and move things over to where they will reduce coupling and increase the cohesion.

因为那时我们将使用相同的语言,所以以前隐式的某些依赖关系可能开始变得更加明确。 我们还可以重构代码并将内容移到它们可以减少耦合和增加内聚力的位置。

Image for post

Now, you might think that this is suggesting that you mix logic with the UI. The reality is that you will have UI-related logic in your application no matter how it is structured. The framework itself cannot change this.

现在,您可能认为这表明您将逻辑与UI混合在一起。 现实情况是,无论应用程序的结构如何,都将具有与UI相关的逻辑。 框架本身不能改变这一点。

But what the framework can do is provide you with tools to make the separation easier: that tool is the Composable function. Functions are something that you’ve likely been using for a long time to separate concerns elsewhere in your code. The skills you’ve acquired to do that type of refactoring and writing reliable, maintainable, clean code — those same skills apply to Composable functions.

但是该框架可以做的就是为您提供简化分离的工具:该工具是Composable函数。 函数是您很长时间以来一直在使用的,用于在代码中其他地方分离问题的东西。 您进行此类重构和编写可靠,可维护,简洁的代码所获得的技能-这些相同的技能也适用于Composable函数。

可组合功能剖析 (Anatomy of a Composable function)

This is an example of a Composable function.

这是可组合函数的示例。

In this case it receives data as parameters from the appData class. Ideally this data is immutable data that the Composable function doesn’t change: the Composable function should be a transform function for that data. Therefore, we can use any Kotlin code to take that data and use it to describe our hierarchy, such as the Header() and Body() calls.

在这种情况下,它从appData类接收数据作为参数。 理想情况下,此数据是Composable函数不变的不变数据:Composable函数应该是该数据的转换函数。 因此,我们可以使用任何Kotlin代码获取该数据并使用它来描述我们的层次结构,例如Header()Body()调用。

This means that we call other Composable functions and those invocations represent the UI in our hierarchy. We are able to use all of the language level primitives that Kotlin has to do things dynamically. We can include if statements and for loops for control flow to deal with the more complicated UI logic.

这意味着我们调用了其他Composable函数,这些调用代表了层次结构中的UI。 我们能够使用Kotlin必须动态执行的所有语言级别的原语。 我们可以包括if语句和for循环,以实现控制流以处理更复杂的UI逻辑。

Composable functions often utilize Kotlin’s trailing lambda syntax, so Body() is a composable function that has a composable lambda as a parameter. That implies a hierarchy or structure, so Body() wraps the set of items here.

可组合函数通常使用Kotlin的尾随lambda语法,因此Body()是一个可组合函数,具有可组合lambda作为参数。 这意味着层次结构或结构,因此Body()在此处包装项目集。

声明式用户界面 (The declarative UI)

Declarative is a buzzword, but an important one. When we talk about declarative programming, we’re talking about it in contrast to imperative programming. Let’s look at an example.

声明式是一个流行词,但很重要。 当我们谈论声明式编程时,我们所谈论的是与命令式编程相反的情况。 让我们来看一个例子。

Consider an email app with an unread messages icon. If there are no messages, the app renders a blank envelope. If there are some messages, we render some paper in the envelope, and if there are 100 messages we render the icon as if it was on fire..

考虑带有未读邮件图标的电子邮件应用程序。 如果没有消息,则该应用程序将呈现一个空白信封。 如果有一些消息,我们在信封中渲染一些纸张,如果有100条消息,我们将图标渲染为好像着火了。

Image for post

With an imperative interface, we might have to write an update count function like this:

在命令式接口的情况下,我们可能必须编写如下的更新计数函数:

In this code, we receive the new count and must figure out how we update the current UI to reflect that state. There are a lot of corner cases here and this logic isn’t easy, even though it’s a relatively simple example.

在此代码中,我们收到了新的计数,并且必须弄清楚如何更新当前的UI以反映该状态。 尽管这里是一个相对简单的示例,但这里有很多极端情况,而且这种逻辑并不容易。

Alternatively, writing this logic in a declarative interface might result in something that looks like this.

或者,在声明性接口中编写此逻辑可能会导致如下所示。

Here we say:

在这里我们说:

  • If the count is over 99, show fire.

    如果计数超过99,则开火。
  • If the count is over 0, show paper,

    如果计数超过0,请显示纸张,
  • If the count is over 0, render a count badge.

    如果计数超过0,则显示计数徽章。

This is what is meant by a declarative API. The code we write describes the UI we want, but not how to transition into that state. The critical thing here is that when writing declarative code like this, you no longer need to worry about what the previous state of your UI was in, you only need to specify what your current state should be. The framework controls how to get from one state to the other, so we no longer need to think about it.

这就是声明性API的含义。 我们编写的代码描述了所需的UI,但没有描述如何转换为该状态。 这里的关键是,当编写这样的声明性代码时,您不再需要担心UI的先前状态是什么,只需要指定当前状态。 该框架控制着如何从一种状态转换为另一种状态,因此我们不再需要考虑它。

组合与继承 (Composition vs Inheritance)

In software development, Composition is how multiple units of simpler code can come together to form a more complex unit of code. In an object-oriented programming model, one of the most common forms of composition is class-based inheritance. In the world of Jetpack Compose, since we are working with just functions instead of classes, the method of composition is quite different, but has many advantages over inheritance. Let’s look at an example.

在软件开发中,组合是如何将多个较简单的代码单元组合在一起以形成一个更复杂的代码单元。 在面向对象的编程模型中,最常见的组合形式之一是基于类的继承。 在Jetpack Compose的世界中,由于我们只使用函数而不是类,因此组合方法大不相同,但是与继承相比,它具有许多优点。 让我们来看一个例子。

Say we have a view and we want to add an input. In the inheritance model our code might look like this:

假设我们有一个视图,我们想添加一个输入。 在继承模型中,我们的代码可能如下所示:

View is the base class. ValidatedInput uses a subclass of Input. To validate a date, DateInput uses a subclass of ValidatedInput. But then there is a challenge: we want to create a date range input, which means validating against two dates — the start and end dates. You could subclass DateInput, but need to do it twice and you can’t do that. This is a limitation of inheritance: we have to have a single parent that we inherit from.

视图是基类。 ValidatedInput使用Input的子类。 为了验证日期,DateInput使用ValidatedInput的子类。 但是接下来是一个挑战:我们要创建一个日期范围输入,这意味着要针对两个日期(开始日期和结束日期)进行验证。 您可以将DateInput子类DateInput ,但需要执行两次,而不能这样做。 这是继承的限制:我们必须有一个继承自其的单亲。

In Compose, this is less of a challenge. Let’s say we start out with a base Input composable:

在Compose中,这并不是一个挑战。 假设我们从一个可组合的基本Input开始:

When we create our ValidatedInput, we just call Input in the body of our function. We can then decorate it with something for validation.

创建ValidatedInput ,我们仅在函数主体中调用Input 。 然后,我们可以用一些东西装饰它以进行验证。

Then for a DataInput we can call ValidatedInput directly.

然后对于DataInput,我们可以直接调用ValidatedInput

Now, when we run into the date range input, we no longer have a challenge: it’s just two calls instead of one.

现在,当我们进入日期范围输入时,我们将不再面临挑战:这只是两个调用而不是一个。

There is no single parent that we compose onto in Compose’s composition model, and that resolves this challenge that we had with the inheritance model.

我们在Compose的组合模型中没有要构成的单亲,这解决了继承模型所面临的挑战。

Another type of composition problem is abstracting over a type of decoration. To illustrate, consider the following inheritance example:

组成问题的另一种类型是对一种装饰类型的抽象。 为了说明,请考虑以下继承示例:

FancyBox is a view that decorates other views, in this case Story and EditForm. We want to code a FancyStory and a FancyEditForm, but how? Do we inherit from FancyBox or do we inherit from Story? It’s unclear because, again, we need one parent for the inheritance chain.

FancyBox是装饰其他视图(在本例中为StoryEditForm视图。 我们要编写FancyStoryFancyEditForm代码,但是如何? 我们是从FancyBox继承还是从Story继承? 尚不清楚,因为同样,我们需要一个父代来继承链。

By contrast, Compose handles this really well.

相比之下,Compose确实处理得很好。

We have a Composable lambda as children, enabling us to define something that wraps another thing. So now, when we want to create FancyStory, we call Story inside the children of FancyBox, and can do the same with FancyEditForm. This is Compose’s composition model.

我们有一个可组合的lambda作为孩子,使我们能够定义包裹另一件事的东西。 因此,现在,当我们要创建FancyStory ,可以在FancyBox的子FancyBox内部调用Story ,并可以使用FancyEditForm进行相同FancyEditForm 。 这是Compose的合成模型。

封装形式 (Encapsulation)

Another thing that Compose accomplishes well is encapsulation. This is what you should be thinking about when you make public APIs of composable functions: the public API of a composable is the set of parameters that it receives, so it doesn’t have control over them. On the other hand, a composable can manage and create state, then pass that state along with any data that it received to other composables as parameters.

Compose做得很好的另一件事是封装。 这是使可组合函数的公共API时应该考虑的问题:可组合函数的公共API是其接收的参数集,因此它无法控制它们。 另一方面,可组合对象可以管理和创建状态,然后将该状态以及它接收到的任何数据作为参数传递给其他可组合对象。

Now, because it’s managing that state, if you want to change the state, you can enable your child composables to signal that change back up using a callback.

现在,由于它正在管理该状态,因此,如果您想更改状态,则可以使您的子可组合物使用回调信号通知更改已备份。

重组 (Recomposition)

This is our way of saying that any Composable function can be re-invoked at any time. If you have this very large Composable hierarchy, when part of your hierarchy changes, you don’t want to have to recompute the entire hierarchy. So Composable functions are restartable and you can use this to do some powerful things.

这是我们的说法,可以随时重新调用任何Composable函数。 如果您有一个非常大的Composable层次结构,那么当层次结构的一部分发生更改时,您就不必重新计算整个层次结构。 因此可组合功能是可重新启动的,您可以使用它来执行一些强大的功能。

For example, here’s a Bind function, something you would see today in Android development.

例如,这里有一个Bind函数,您今天会在Android开发中看到。

We have a LiveData that we want to subscribe the view to. To do that, we call the observe method with a lifecycle owner, then pass in a lambda. The lambda gets called every time LiveData updates and when that happens, we want to update the views.

我们有一个LiveData ,我们要订阅该视图。 为此,我们使用生命周期所有者调用observe方法,然后传入一个lambda。 每次LiveData更新时都会调用lambda,并且发生这种情况时,我们要更新视图。

With Compose, we can invert this relationship.

使用Compose,我们可以反转这种关系。

There is a similar Messages Composable that receives LiveData and a call to Compose’s observeAsState method. The observeAsState method will map the LiveData<T> into a State<T>. That means you can use the value in the surrounding body of the function. The State instance is subscribed to the LiveData instance, meaning it will update whenever the LiveData updates. This also means that wherever the State instance is read, the surrounding composable function which it is read in will be automatically subscribed to these changes. The end result is that there is no longer any need to specify a LifecycleOwner or an update callback, as the Composable can implicitly serve as both.

有一个类似的Messages Composable,它接收LiveData并调用Compose的observeAsState方法。 observeAsState方法会将LiveData<T>映射到State<T> 。 这意味着您可以在函数周围使用该值。 State实例已订阅LiveData实例,这意味着只要LiveData更新,它就会更新。 这也意味着,无论在何处读取State实例,都将自动订阅这些实例所周围的可组合函数,这些函数将被预订。 最终结果是,不再需要指定LifecycleOwner或update回调,因为Composable可以隐式地同时用作两者。

最后的想法 (Final thoughts)

Compose provides a modern approach to defining your UI that enables you to separate concerns effectively. Because Composable functions are so similar to normal kotlin functions, the tools with which you write and refactor them will fit neatly into your kit of Android development skills.

Compose提供了一种现代的方法来定义UI,使您能够有效地分离关注点。 由于可组合功能与普通的kotlin功能非常相似,因此您编写和重构它们的工具将完全适合您的Android开发技能套件。

In the next post, I’m going to shift the focus to some of the implementation details of Compose and its compiler. For additional resources on Compose, discover more here.

在下一篇文章中,我将把重点转移到Compose及其编译器的一些实现细节上。 有关Compose的其他资源,请在此处找到更多。

翻译自: https://medium.com/androiddevelopers/understanding-jetpack-compose-part-1-of-2-ca316fe39050

jetpack tx2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值