android多状态视图
Works with Non-inherited and Inherited Custom View
与非继承和继承的自定义视图一起使用
In Android, there are some behaviors that we called “State Changes” which can occur from many conditions. Whether the platform might kill the application to reclaim the memory for the currently active app or Configuration Changes which recreated the Context in running Activity with the new configurations. To keep the data in your application from these changes, we have to handle the state in each Activity, Fragment and View.
在Android中,我们将某些行为称为“状态更改”,这些行为可能会在许多情况下发生。 平台是否可能杀死应用程序以回收当前活动应用程序的内存,或者是配置更改(使用新配置在运行Activity时重新创建了上下文)。 为了使应用程序中的数据不受这些更改的影响,我们必须处理每个Activity,Fragment和View中的状态。
Using existing Views from Android Frameworks or 3rd-party Libraries (which handle the state changes correctly), the state changes will be handled by itself. But when you create your own (called “Custom View”) you have to put the additional code for handling the state changes in your Custom View.
使用来自Android框架或第三方库(可正确处理状态更改)的现有视图,状态更改将由其自身处理。 但是,当您创建自己的(称为“自定义视图”)时,必须在“自定义视图”中放置用于处理状态更改的其他代码。
For example, my PostView
contains 3 states (title
, description
and dividerColorResId
) inside.
例如,我的PostView
包含3个状态( title
, description
和dividerColorResId
)。
class PostView : FrameLayout {
private var title: String? = null
private var description: String? = null
private var dividerColorResId: Int = 0
...
}
To support the state changes, I have to implement onSaveInstanceState
and onRestoreInstanceState
and handle all states in there.
为了支持状态更改,我必须实现onSaveInstanceState
和onRestoreInstanceState
并在那里处理所有状态。
class PostView : FrameLayout {
private var title: String? = null
private var description: String? = null
private var dividerColorResId: Int = 0
...
override fun onSaveInstanceState(): Parcelable? {
val superState: Parcelable? = super.onSaveInstanceState()
superState?.let {
val state = SavedState(superState)
state.title = this.title
state.description = this.description
state.dividerColorResId = this.dividerColorResId
return state
} ?: run {
return superState
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
when (state) {
is SavedState -> {
super.onRestoreInstanceState(state.superState)
this.title = state.title
this.description = state.description
this.dividerColorResId = state.dividerColorResId
// Restore view's state here
}
else -> {
super.onRestoreInstanceState(state)
}
}
}
internal class SavedState : BaseSavedState {
var title: String? = null
var description: String? = null
var dividerColorResId: Int = 0
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel) : super(source) {
title = source.readString()
description = source.readString()
dividerColorResId = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeString(title)
out.writeString(description)
out.writeInt(dividerColorResId)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}
As you see, handling state changes, you need SavedState
which extends from BaseSavedState
to transform the data to Parcelable when onSaveInstanceState
called, and onRestoreInstanceState
to restore the data back from Parcelable.
正如你看到的,处理状态的变化,你需要SavedState
从延伸BaseSavedState
将数据转换到Parcelable时onSaveInstanceState
调用, onRestoreInstanceState
恢复从Parcelable回数据。
But the Android system does not hold the Parcelable while the state changes directly. Parcelable will be converted to Parcel and held in the system, and convert back to Parcelable when state restoring. That is why we have to declare the CREATOR
which create from Parcelable.Creator
to convert Parcel back to SavedState
(Parcelable) by createFromParcel
但是,当状态直接更改时,Android系统不会保留Parcelable。 Parcelable将转换为Parcel并保留在系统中,并在恢复状态时转换回Parcelable。 这就是为什么我们要声明的CREATOR
从创建Parcelable.Creator
转换包裹回SavedState
由(Parcelable) createFromParcel
This code is a popular example of handling state changes in Custom View.
此代码是在“自定义视图”中处理状态更改的流行示例。
Unfortunately, it is not working with Inherited Custom View.
不幸的是,它不能与Inherited Custom View一起使用 。
继承的自定义视图,如何? (Inherited Custom View, How?)
Instead of a single (non-inherited) Custom View, separated the code into a base class and derived class.
将代码分成基类和派生类,而不是单个(非继承)自定义视图。
The code in BasePostView
will be
BasePostView
的代码将是
abstract class BasePostView : FrameLayout {
private var title: String? = null
private var description: String? = null
...
override fun onSaveInstanceState(): Parcelable? {
val superState: Parcelable? = super.onSaveInstanceState()
superState?.let {
val state = SavedState(superState)
state.title = this.title
state.description = this.description
return state
} ?: run {
return superState
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
when (state) {
is SavedState -> {
super.onRestoreInstanceState(state.superState)
this.title = state.title
this.description = state.description
// Update view's state here
}
else -> {
super.onRestoreInstanceState(state)
}
}
}
internal class SavedState : BaseSavedState {
var title: String? = null
var description: String? = null
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel) : super(source) {
title = source.readString()
description = source.readString()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeString(title)
out.writeString(description)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}
And RegularPostView
will be
而RegularPostView
将是
class RegularPostView : BasePostView {
private var dividerColorResId: Int = 0
...
override fun onSaveInstanceState(): Parcelable? {
val superState: Parcelable? = super.onSaveInstanceState()
superState?.let {
val state = SavedState(superState)
state.dividerColorResId = this.dividerColorResId
return state
} ?: run {
return superState
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
when (state) {
is SavedState -> {
super.onRestoreInstanceState(state.superState)
this.dividerColorResId = state.dividerColorResId
// Update view's state here
}
else -> {
super.onRestoreInstanceState(state)
}
}
}
internal class SavedState : BaseSavedState {
var dividerColorResId: Int = 0
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel) : super(source) {
dividerColorResId = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(dividerColorResId)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}
These 2 classes handle state changes in the same way with a single Custom View. title
and description
handled by BasePostView
and dividerColorResId
handled by RegularPostView
.
这两个类通过单个“自定义视图”以相同的方式处理状态更改。 title
和description
由BasePostView
处理, dividerColorResId
由RegularPostView
处理。
And everything seems to work well until the app crashes while state changes by the application process.
在状态因应用程序进程而变化之前,应用程序崩溃之前,一切似乎都运行良好。
到底是怎么回事? (What is going on?)
Using BaseSavedState
for SavedState
and Parcelable.Creator
for CREATOR
in Inherited Custom View only works well when the state changes by configuration changes, not state changes by the application process.
使用BaseSavedState
为SavedState
和Parcelable.Creator
对CREATOR
在继承的自定义视图,只有当国家通过配置的变化,而不是由应用程序状态的变化而改变效果很好。
In Android, when users put your app in the background for a long time, the Android system might reclaim the memory by killing your application’s process. But also be able to restore the data when the user opens your app again.
在Android中,当用户长时间将您的应用程序置于后台时,Android系统可能会通过终止应用程序的进程来回收内存。 但是当用户再次打开您的应用程序时,也能够还原数据。
class RegularPostView : BasePostView {
...
internal class SavedState : BaseSavedState {
...
constructor(source: Parcel) : super(source) {
dividerColorResId = source.readInt()
}
...
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
...
}
}
}
}
When restored from state changes by the application process, ClassLoader in CREATOR
is required for Parcel to Parcelable converting. But there is no ClassLoader in the above example. So RegularPostView
can not restore the state in BasePostView
(from super
).
当应用程序从状态更改中恢复时, CREATOR
ClassLoader是宗地到宗地可转换的必需项。 但是上面的示例中没有ClassLoader。 所以RegularPostView
无法恢复的状态BasePostView
(从super
)。
Restoring from state changes by the application process, ClassLoader in CREATOR
is required for Parcel to Parcelable converting. But there is no ClassLoader in the above example. So RegularPostView
can not restore the state in BasePostView
(from super
).
通过应用程序从状态更改恢复,将宗地转换为可宗地时需要CREATOR
ClassLoader。 但是上面的示例中没有ClassLoader。 所以RegularPostView
无法恢复的状态BasePostView
(从super
)。
To test your app in this behavior, just put your app into background, Click at Terminates selected Android Application
in Android Studio’s Logcat, then re-open your app again.
要以这种行为测试您的应用程序,只需将您的应用程序置于后台,在Android Studio的Logcat中单击“ Terminates selected Android Application
,然后再次重新打开您的应用程序。
Before talking about how to solve this problem. Let’s talk about…
在谈论如何解决这个问题之前。 让我们来谈谈…
为什么我使用继承的自定义视图而不是单个自定义视图? (Why am I using the Inherited Custom View instead of a single Custom View?)
From the previous example code, you might say “Just keep all code in one class because it’s Custom View, nothing more”.
在前面的示例代码中,您可能会说“将所有代码保留在一个类中,因为它是Custom View,仅此而已”。
My reason for using the Inherited Custom View is the RoundCornerProgressBar library for the customized progress bar.
我使用继承的自定义视图的原因是自定义进度条的RoundCornerProgressBar库 。
In this library, I created 6 types of progress bars with some logic in common. So using Inherited Custom View is more suitable to maintain it in the future. (reusable + maintainable = 👍👍👍)
在这个库中,我创建了6种类型的进度条,它们具有一些共同的逻辑。 因此,使用继承的自定义视图更适合将来进行维护。 (可重用+可维护=👍👍👍)
All classes also have their own states to handle. So BaseSavedState
and Parcelable.Creator
do not work for my library anymore.
所有类也都有自己的状态要处理。 所以BaseSavedState
和Parcelable.Creator
没有为我的图书馆工作了。
Then, let’s get back to the previous code.
然后,让我们回到前面的代码。
在继承的自定义视图中解决此问题 (To solve this problem in Inherited Custom View)
First, replace the BaseSavedState
with AbsSavedState
(which allows you to ensure the state of all classes along the hierarchy is saved) to all classes in Custom View’s hierarchy.
首先 ,用AbsSavedState
(允许您确保保存层次结构中所有类的状态)替换BaseSavedState
到“自定义视图”层次结构中的所有类。
abstract class BasePostView : FrameLayout {
...
internal class SavedState : AbsSavedState {
var title: String? = null
var description: String? = null
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
title = source.readString()
description = source.readString()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeString(title)
out.writeString(description)
}
...
}
}
class RegularPostView : BasePostView {
...
internal class SavedState : AbsSavedState {
var dividerColorResId: Int = 0
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
dividerColorResId = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(dividerColorResId)
}
...
}
}
AbsSavedState
also has a constructor(source: Parcel, loader: ClassLoader?)
which supports ClassLoader. And must be AbsSavedState
from androidx.customview.view.AbsSavedState
for backward compatibility because the constructor of android.view.AbsSavedState
is available in API level 24 or higher.
AbsSavedState
也有一个支持ClassLoader的constructor(source: Parcel, loader: ClassLoader?)
。 并且必须为AbsSavedState
的androidx.customview.view.AbsSavedState
以便向后兼容,因为android.view.AbsSavedState
的构造函数在API级别24或更高版本中可用。
androidx.customview.view.AbsSavedState
is in the CustomView library of Android Jetpack libraries and also included in the AppCompat library.
androidx.customview.view.AbsSavedState
位于Android Jetpack库的CustomView库中,并且也包含在AppCompat库中。
Second, replace the Parcelable.Creator
with Parcelable.ClassLoader
(which allows the Creator to receive the ClassLoader the object is being created in) to all classes in Custom View’s hierarchy.
二 ,更换Parcelable.Creator
与Parcelable.ClassLoader
(允许造物主接收正在创建的对象的类加载器)自定义视图中的层次结构的所有类。
abstract class BasePostView : FrameLayout {
...
internal class SavedState : AbsSavedState {
...
companion object {
@JvmField
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
return SavedState(source, loader)
}
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source, null)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}
class RegularPostView : BasePostView {
...
internal class SavedState : AbsSavedState {
...
companion object {
@JvmField
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
return SavedState(source, loader)
}
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source, null)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}
Then, test the Custom View in every state changes behavior again. And it finally works! 🎉🎉🎉
然后,在每种状态下再次测试自定义视图更改行为。 终于成功了! 🎉🎉🎉
结论 (Conclusion)
To handle state changes in Inherited Custom View, Using AbsSavedState
and Parcelable.ClassLoaderCreator
which allows us to save/restore the state of all classes along the hierarchy.
为了处理状态改变继承自定义视图,使用AbsSavedState
和Parcelable.ClassLoaderCreator
这使我们能够保存/恢复沿着层次结构中所有类的状态。
Even the single Custom View, if your project contains both Custom Views, using AbsSavedState
and Parcelable.ClassLoaderCreator
in all of them is better than separating the code style between non-inherited and inherited.
即使是单一的自定义视图,如果你的项目包含两个自定义视图,使用AbsSavedState
和Parcelable.ClassLoaderCreator
在所有这些比之间分离的代码风格更好的非继承和继承。
No Inherited Custom View in your project? Don’t worry. BaseSavedState
and Parcelable.Creator
still work well for your Custom View, no need to replace them.
您的项目中没有继承的自定义视图? 不用担心 BaseSavedState
和Parcelable.Creator
仍然为您的自定义视图,无需更换他们工作得很好。
Finally, the example code of this article. See the link below
最后,本文的示例代码。 见下面的链接
android多状态视图