ios 调整视图图层_视图层:状态和视图的故事

本文介绍如何使用Model-ViewModel模式构建Android应用程序的视图层,遵循减少代码量和增强视图声明性的原则。文章详细阐述了如何定义视图状态及其实现方式,并解释了ViewModel的作用。

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

ios 调整视图图层

This article is a description of how the view layer of an Android application can be architected and implemented using the Model-ViewModel pattern with a key principle: make the views as declarative as possible. The application I use to expose the ideas is MoviesPreview, which is an application that allows a user to browse the data exposed by The Movie DB API to see different information about movies, actors/actresses, and some other features. The full code of the project can be found in this Github repository.

本文描述了如何使用Model-ViewModel模式并遵循以下关键原则来构建和实现Android应用程序的视图层:尽可能使视图具有声明性。 我用来展示想法的应用程序是MoviesPreview ,该应用程序允许用户浏览Movie DB API公开的数据,以查看有关电影,演员/女演员和其他某些功能的不同信息。 该项目的完整代码可在此Github存储库中找到。

原则 (Principles)

I’ve based the implementation of the view layer using MVVM, following the basic principles or guidelines:

我已经按照MVVM的基本原理或准则对视图层进行了实现:

  • The less code, the better. The view layer of an application should only take care of one thing and one thing only: render the view state.

    代码越少越好。 应用程序的视图层应该只处理一件事,并且仅处理一件事:呈现视图状态。
  • The states that a view can present at any given moment should be as clear as possible and it should be as structured as possible in order to make it easy for the developer when reading the code.

    视图在任何给定时刻可以呈现的状态应尽可能清晰,并且应尽可能结构化,以使开发人员在阅读代码时更加容易。
  • The view layer code should be as declarative as possible. This means that no control block should be found in the view layer.

    视图层代码应尽可能声明性。 这意味着在视图层中找不到控制块。

查看状态及其表示 (View state and its representation)

The state of a View is defined as the visibility and the content of the View. The details of positioning, margins, sizes, etc. are not part of the state description of a view. Those are properties that are intrinsic, independently of the state it can assume.

View的状态定义为View的可见性和内容。 定位,边距,大小等详细信息不属于视图状态描述。 这些是固有的属性,与它可以假定的状态无关。

Ideally, Each Fragment/Activity has a one to one mapping with a ViewState class. EachViewState class contains a representation of the state of every individualView present in the view hierarchy of the layout XML file of the Fragment/Activity. This way is really easy to reproduce any state of the application that is presented to the user without the need to reproduce the inner state of the application (the Model). It also allows making a declarative representation of what the Fragment/Activity can show to the user to facilitate reading the code.

理想情况下,每个“片段/活动”都具有与ViewState类的一对一映射。 每个ViewState类都包含Fragment / Activity布局XML文件的视图层次结构中每个单独View的状态的表示。 这种方式真的很容易重现呈现给用户的应用程序的任何状态,而无需重现应用程序的内部状态(模型)。 它还允许对Fragment / Activity可以显示给用户的内容进行声明式表示,以方便阅读代码。

For simple view hierarchies, this approach is easy to apply, follow, and read. Of course, in the real world view hierarchies are far more complex than this. For these situations, we can take this representation to a deeper level of granularity by wrapping similar properties of the state in other view states:

对于简单的视图层次结构,此方法易于应用,遵循和阅读。 当然,在现实世界中,层次结构要比这复杂得多。 对于这些情况,我们可以通过将状态的相似属性包装在其他视图状态中,将表示形式更深层次地进行:

Image for post
Granular ViewState
细粒度的ViewState

On occasions, a single ViewState class per Activity/Fragment is hard to achieve since it might complicate the logic needed to update specific sections of the view hierarchy. For instance: in MoviesPreview, there is a screen that shows the details of a movie. It contains an image with the poster of the movie plus the synopsis and a couple of other details that are specific to the movie. It also gives the user the possibility to perform actions with the movie: add it to the favorite movies list, add it to a watch-list or rate the movie.

有时,很难实现每个“活动/片段”的单个ViewState类,因为这可能会使更新视图层次结构的特定部分所需的逻辑复杂化。 例如:在MoviesPreview中,有一个屏幕显示电影的详细信息。 它包含带有电影海报的图像,简介和有关电影的其他一些细节。 它还使用户可以对电影执行操作:将其添加到“喜欢的电影”列表中,将其添加到观看列表或对电影进行评分。

Image for post

It was really hard to maintain the whole state of the screen in a single class. Even more, an animation is executed every time the user clicks in the button with the icon. Having to recreate and re-render the whole screen for every user touch on the button was extremely hard to model and control. The decision then was to break the view into different ViewStates , each one of them related to a specific context: one for the static information of the screen and one for the dynamic section. The result was a much simpler interaction between the View Layer and the View Model:

很难在一个类中维护整个屏幕状态。 甚至,每当用户单击带有图标的按钮时,都会执行动画。 必须为每个用户触摸按钮重新创建和重新渲染整个屏幕,这很难建模和控制。 然后决定将视图分为不同的ViewStates ,每个视图状态都与特定的上下文相关:一个用于屏幕的静态信息,另一个用于动态部分。 结果是视图层和视图模型之间的交互更加简单:

Image for post
ViewState breakdown for movie details
电影详细信息的ViewState细分

In order to have clear transitions between one ViewState and another (for instance, when the visibility of a view changes), each ViewState class has a set of factory methods defined. By invoking those factory methods, we make the code to update the view state fairly simple and we make sure that a developer does not need to explore a whole ViewModel in order to detect each state a View can go through.

为了在一个ViewState与另一个ViewState之间有清晰的过渡(例如,当视图的可见性更改时),每个ViewState类都定义了一组工厂方法。 通过调用这些工厂方法,我们使更新视图状态的代码相当简单,并且确保开发人员无需探索整个ViewModel即可检测视图可以通过的每个状态。

ViewModel控制状态 (ViewModel to control the state)

The other important part of the UI architecture is the ViewModel. As the name says it explicitly, all ViewModels in the architecture are implementing the ViewModel class from the Android Architecture Components in order to make them lifecycle aware and to avoid complications of handling the destruction and recreation of views and its containers.

UI体系结构的另一个重要部分是ViewModel。 顾名思义,该体系结构中的所有ViewModels都从Android Architecture Components实现了ViewModel类,以使其具有生命周期意识,并避免了处理视图及其容器的破坏和重新创建的麻烦。

Besides this (very important) inherited responsibility, the ViewModel has one very specific responsibility that is control flow. It takes care of executing the UseCases available in the Domain Layer (the Model in MVVM) and sends the ViewState to the View Layer to render.

除了这一(非常重要的)继承职责外, ViewModel还有一个非常具体的职责就是控制流。 它负责执行域层(MVVM中的模型)中可用的UseCases,并将ViewState发送到View层进行渲染。

The ViewModel API is fairly simple: it has only one output that is the ViewState in the form of Android’s LiveData to take a reactive approach and as many inputs as needed to intercept user actions. This means that for every view the user can interact with, there is a corresponding public method in the ViewModel that the View Layer takes care of mapping. That way, for every user action, there is a consequence that handled by the ViewModel . That consequence might be either a state update or a request to navigate to another screen.

ViewModel API非常简单:它只有一个输出,即Android的LiveData形式的ViewState ,可以采取一种React性的方法,并且可以根据需要输入任意数量的输入来拦截用户操作。 这意味着对于用户可以与之交互的每个视图, ViewModel中都有一个相应的公共方法,View Layer负责映射。 这样,对于每个用户操作,都有ViewModel处理的结果。 结果可能是状态更新,也可能是导航到另一个屏幕的请求。

So, basically, every time the user performs an action (like viewing the screen for the first time) the View Layer executes a method in the ViewModel that triggers a state update in the application.

因此,基本上,每次用户执行操作(例如第一次查看屏幕)时,View Layer都会在ViewModel中执行一个方法,该方法触发应用程序中的状态更新。

Image for post

There are two extra responsibilities that the ViewModel have:

ViewModel有两个额外的职责:

  • Map domain entities resulted from UseCase execution into ViewStates: the ViewModel makes the translation between the result of the UseCase and the ViewState in order to send the ViewState for rendering. We could introduce a state-reducer or a mapper to have this responsibility, but I decided to minimize the number of dependencies.

    将UseCase执行产生的地图域实体映射到ViewStates: ViewModel在UseCase的结果和ViewState之间进行转换,以便发送ViewState进行渲染。 我们可以引入状态缩减器或映射器来承担此责任,但我决定将依赖关系的数量减至最少。

  • Control the thread in which each action is executed: this is kind of an inherited responsibility. Since the application is using Koltin’s coroutines to execute long-term actions, the ViewModel base class from the Android Architecture Components comes with built-in logic to handle these coroutines.

    控制执行每个动作的线程:这是一种继承的责任。 由于应用程序使用Koltin的协程执行长期操作,因此Android体系结构组件的ViewModel基类带有内置逻辑来处理这些协程。

数据绑定是View和ViewState之间的粘合剂 (Data Binding as the glue between Views and ViewState)

Android Data Binding library is used to transform ViewState classes into View properties, without the need to have that responsibility in the Fragment/Activity. By using this technique, the code to render a ViewState can be as simple as this:

Android数据绑定库用于将ViewState类转换为View属性,而无需在Fragment / Activity中承担该责任。 通过使用这种技术,呈现ViewState的代码可以像这样简单:

private fun renderViewState(viewState: PersonViewState) {
setScreenTitle(viewState.screenTitle)
viewBinding?.viewState = viewState
}

This is because the variable used in the XML declaration of the Views it is no other than the ViewState that represents the entire hierarchy of the layout. All we have to do in order to render the view state is pass the new ViewState into the viewBinding created when the View is initialized and the rest is done in a declarative way in the layout XML:

这是因为在视图的XML声明中使用的variable不是表示布局的整个层次结构的ViewState 。 为了呈现视图状态,我们要做的就是将新的ViewState传递到初始化View时创建的viewBinding ,其余的以声明方式在布局XML中完成:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewState"
type="com.jpp.mpcredits.CreditsViewState" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.jpp.mpdesign.views.MPErrorView
android:id="@+id/creditsErrorView"
android:layout_width="0dp"
android:layout_height="0dp"
app:animatedVisibility="@{viewState.errorViewState.visibility}"
app:asConnectivity="@{viewState.errorViewState.isConnectivity}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onRetry="@{viewState.errorViewState.errorHandler}" />
<ProgressBar
android:id="@+id/creditsLoadingView"
style="@style/MPProgressBar"
app:animatedVisibility="@{viewState.loadingVisibility}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/creditsRv"
android:layout_width="0dp"
android:layout_height="0dp"
app:animatedVisibility="@{viewState.creditsViewState.visibility}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/list_item_credits" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

One rule is defined and respected as a rule of thumb in order to have this interaction clear:

为了清楚地进行这种交互,已定义并遵守了一条经验法则:

  • Data Binding must be used to map ViewState properties with View properties without the usage of Data Binding expressions.

    必须使用数据绑定来将ViewState属性与View属性进行映射,而不使用数据绑定表达式。

By respecting this rule, we ensure that there are no business rules hidden in the XML layouts.

通过遵守此规则,我们确保XML布局中没有隐藏任何业务规则。

翻译自: https://medium.com/swlh/the-view-layer-a-story-of-states-and-views-f5c3ffbac96f

ios 调整视图图层

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值