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

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中,有一个屏幕显示电影的详细信息。 它包含带有电影海报的图像,简介和有关电影的其他一些细节。 它还使用户可以对电影执行操作:将其添加到“喜欢的电影”列表中,将其添加到观看列表或对电影进行评分。

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
,每个视图状态都与特定的上下文相关:一个用于屏幕的静态信息,另一个用于动态部分。 结果是视图层和视图模型之间的交互更加简单:

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
中执行一个方法,该方法触发应用程序中的状态更新。

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 withView
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 调整视图图层