前言
在上一篇文章Android狀態(tài)保存與恢復(fù)流程 完全解析留拾,筆者詳細(xì)地介紹了Activity戳晌、Fragment、View等的狀態(tài)保存與恢復(fù)流程痴柔,相信大家對(duì)狀態(tài)的保存與恢復(fù)都有了一定熟悉沦偎。而這篇文章就著重介紹自定義View該怎樣保存與恢復(fù)狀態(tài),因?yàn)槊總€(gè)自定義View都是不同的情況竞帽,所以我們一般需要重寫View的onSaveInstanceState()或onRestoreInstanceState()方法來實(shí)現(xiàn)我們需要的邏輯扛施。那么本文的核心就是討論怎樣重寫上面兩個(gè)方法來保存或恢復(fù)我們需要的數(shù)據(jù)。
自定義View的狀態(tài)保存
我們知道屹篓,當(dāng)Activity調(diào)用了onSaveInstanceState方法后疙渣,便會(huì)對(duì)它的View Tree進(jìn)行保存,而進(jìn)一步對(duì)每一個(gè)子View調(diào)用其onSaveInstanceState()方法來保存狀態(tài)堆巧。我們?cè)诓恢涝撛鯓酉率值臅r(shí)候妄荔,可以參考一下Android源碼,因?yàn)樗泻芏郬idget是繼承自View的谍肤,也就是Android系統(tǒng)自身的“自定義View”啦租,我們可以看看它們的onSaveInstanceState()方法是怎樣寫的,說不定可以啟發(fā)我們的思路荒揣。這里筆者選取一個(gè)比較簡(jiǎn)單的控件:CheckBox篷角,它的功能不用筆者多說了。它繼承自CompoundButton系任,直接看CompoundButton#onSaveInstanceState:
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.checked = isChecked();
return ss;
}
onSaveInstanceState()方法返回Parcelable對(duì)象恳蹲,也即是序列化對(duì)象虐块,是Android提供的一種序列化方式,如果不了解序列化的讀者可以看這篇文章Android IPC機(jī)制(一):序列化與反序列化嘉蕾,這里就不展開細(xì)說了贺奠。我們回到上面的源碼,首先調(diào)用了super.onSaveInstanceState()方法來獲取一個(gè)Parcelable對(duì)象错忱,接著把superState傳遞進(jìn)SavedState的構(gòu)造方法儡率,構(gòu)建了一個(gè)SavedState,并且設(shè)置SavedState的checked屬性為當(dāng)前isChecked()方法的返回值以清,也即把狀態(tài)保存在SavedState里面儿普,并且返回SavedState。所以說SavedState是一個(gè)實(shí)現(xiàn)了Parcelable接口的類掷倔,我們來看看:
static class SavedState extends BaseSavedState {
boolean checked;
/**
* Constructor called from {@link CompoundButton#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
checked = (Boolean)in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
該SavedState繼承自BaseSaveState箕肃,有一個(gè)成員變量checked,這就是我們需要保存的自定義View的狀態(tài)了今魔,而且SavedState的結(jié)構(gòu)與實(shí)現(xiàn)Parcelable接口的類的結(jié)構(gòu)基本是一樣的勺像,也就是說SavedState的父類必然實(shí)現(xiàn)了Parcelable接口,所以我們?nèi)绻枰4嫖覀兊淖远x狀態(tài)错森,我們可以仿照CompoundButton吟宦,在自定義View內(nèi)實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類SavedState,并繼承自BaseSavedState涩维,這樣就能得到一個(gè)Parcelable對(duì)象了殃姓。當(dāng)然,你也可以直接寫一個(gè)類實(shí)現(xiàn)Parcelable接口來保存狀態(tài)瓦阐,這樣一般也是可以的蜗侈。
接下來舉一個(gè)自定義View的例子。這個(gè)例子是筆者之前制作的一個(gè)自定義View:BannerViewPager睡蟋,之前并沒有考慮狀態(tài)保存的事情踏幻,導(dǎo)致每次旋轉(zhuǎn)屏幕后都回到了初始狀態(tài),這次利用學(xué)到的狀態(tài)保存的知識(shí)戳杀,為該自定義View加上保存狀態(tài)的功能该面。下面是筆者的重寫的onSaveInstanceState()方法以及SavedState內(nèi)部類:
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
SavedState ss = new SavedState(parcelable);
//把當(dāng)前的位置保存進(jìn)SavedState
ss.currentPosition = mCurrentPosition;
return ss;
}
static class SavedState extends BaseSavedState{
//當(dāng)前的ViewPager的位置
int currentPosition;
public SavedState(Parcel source) {
super(source);
currentPosition = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(currentPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>(){
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
邏輯還算是比較簡(jiǎn)單的,關(guān)鍵在于onSaveInstanceState()方法內(nèi)把需要的數(shù)據(jù)放進(jìn)SavedState即可信卡,而SavedState是繼承自BaseSavedState隔缀,我們根據(jù)需要添加新的屬性即可,比如這里筆者添加了currentPosition屬性傍菇,并在相應(yīng)的SavedState(Parcel)和writeToParcel(Parcel,int)方法內(nèi)對(duì)該屬性進(jìn)行了操作猾瘸,只要了解Parcelable即可輕松實(shí)現(xiàn)。
自定義View的狀態(tài)恢復(fù)
上面對(duì)自定義View的狀態(tài)進(jìn)行了保存,接下來我們就要恢復(fù)這些狀態(tài)牵触。既然有了已經(jīng)保存的狀態(tài)仔蝌,那么恢復(fù)狀態(tài)也很簡(jiǎn)單了,因?yàn)樵趏nRestoreInstanceState(Parcelable)方法內(nèi)荒吏,根據(jù)傳遞進(jìn)來的Parcelable參數(shù),我們可以拿到我們之前保存的數(shù)據(jù)渊鞋,再根據(jù)需要進(jìn)行賦值或者調(diào)用某些方法來恢復(fù)狀態(tài)就行了绰更。比如說,CheckBox锡宋,之前保存的是是否選擇了這個(gè)CheckBox的狀態(tài)儡湾,那么恢復(fù)就應(yīng)該對(duì)CheckBox重新選中或不選中。同樣以BannerViewPager為例执俩,重寫onRestoreInstanceState(Parcelable)方法如下:
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
//調(diào)用別的方法徐钠,把保存的數(shù)據(jù)重新賦值給當(dāng)前的自定義View
mViewPager.setCurrentItem(ss.currentPosition);
}
總結(jié)
從上面看出,保存與恢復(fù)自定義View的狀態(tài)并不能役首,核心在于onSaveInstanceState()方法尝丐、onRestoreInstanceState(Parcelable)方法以及SavedState的實(shí)現(xiàn),當(dāng)然了衡奥,SavedState不是必須繼承自BaseSavedState的爹袁,我們完全可以自行實(shí)現(xiàn)Parcelable接口。
遇到的問題
最后矮固,再來談?wù)劰P者遇到的問題失息。如果按照上面所說的去做,一般都能實(shí)現(xiàn)狀態(tài)的保存與恢復(fù)档址。但是有時(shí)候遇到的某些特殊情況也會(huì)讓人感到很困惑盹兢。筆者曾繼承了RecyclerView,對(duì)它的功能拓展守伸,目的是添加HeaderView绎秒,這種情況很常見,比如ListView就可以添加HeadView尼摹。然后筆者把BannerViewPager作為HeaderView添加進(jìn)RecyclerView里面替裆,換句話說HeaderView是RecyclerView的一個(gè)子item view。然而問題出現(xiàn)了窘问,每次旋轉(zhuǎn)屏幕后BannerViewPager的onSaveInstanceState()和onRestoreInstanceState(Parcelable)方法都不會(huì)被調(diào)用辆童,而RecyclerView的是正常調(diào)用,也即是說惠赫,保存與恢復(fù)的事件并沒有傳遞給BannerViewPager把鉴。為了探究出現(xiàn)這個(gè)問題的原因,筆者開始查看RecyclerView的源碼。從上一篇文章我們知道庭砍,保存狀態(tài)的事件是從View#dispatchSaveInstanceState開始的场晶,查看RecyclerView的源碼,發(fā)現(xiàn)它重寫了dispatchSaveInstanceState方法怠缸,即RecyclerView#dispatchSaveInstanceState:
/**
* Override to prevent freezing of any views created by the adapter.
*/
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
dispatchFreezeSelfOnly(container);
}
可以發(fā)現(xiàn)诗轻,RecyclerView并沒有遍歷它的子View來保存它們的狀態(tài),而是直接調(diào)用了ViewGroup#dispatchFreezeSelfOnly方法:
/**
* Perform dispatching of a {@link #saveHierarchyState(android.util.SparseArray)} freeze()}
* to only this view, not to its children. For use when overriding
* {@link #dispatchSaveInstanceState(android.util.SparseArray)} dispatchFreeze()} to allow
* subclasses to freeze their own state but not the state of their children.
*
* @param container the container
*/
protected void dispatchFreezeSelfOnly(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
}
從源碼注釋可以看出揭北,如果ViewGroup不需要保存它的子View的狀態(tài)扳炬,可以調(diào)用dispatchFreezeSelfOnly方法,該方法直接調(diào)用了View#dispatchSaveInstanceState方法搔体,僅僅保存了自身的狀態(tài)恨樟。所以到這里也就明白了,RecyclerView重寫了ViewGroup的dispatchSaveInstanceState方法疚俱,沒有對(duì)子View進(jìn)行遍歷保存狀態(tài)劝术,因此我們添加的HeaderView自然不會(huì)保存狀態(tài),更別說恢復(fù)狀態(tài)了呆奕。因此养晋,以后如果我們的自定義View要嵌套在另一個(gè)自定義View內(nèi)作為子View,必須注意該父容器有沒有重寫了dispatchSaveInstanceState方法梁钾,或者說有沒有把保存-恢復(fù)事件傳遞給子View匙握。注意這個(gè)問題后,就能避免很多麻煩了陈轿。
好了圈纺,到現(xiàn)在本文也講述了自定義View的保存恢復(fù)狀態(tài)一般寫法以及可能會(huì)遇到的問題,最后感謝你的閱讀~有問題歡迎指出麦射。