Android自定义View保存状态时如何将Drawable加入Parcelable对象?
问题
我正在开发Android应用的自定义View,该View包含一个存储在res/drawable目录下的VectorDrawable对象。为保存View状态,我创建了SavedState类用于onSaveInstanceState和onRestoreInstanceState方法中,代码如下:
private static class SavedState extends BaseSavedState { Drawable picture; Float degree = 0f; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); degree = source.readFloat(); picture = source.readParcelable(getClass().getClassLoader()); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeFloat(degree); out.writeParcelable((Parcelable) picture, flags); } 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]; } }; } @Nullable @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState myState = new SavedState(superState); myState.degree = this.degree; myState.picture = this.picture; return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); this.degree = savedState.degree; this.picture = savedState.picture; invalidate(); }
但启动应用并翻转屏幕时,出现错误:java.lang.ClassCastException: android.graphics.drawable.VectorDrawable cannot be cast to android.os.Parcelable。开发环境为Android API 32、Java 8。
我尝试将Drawable转为Bitmap:
protected Parcelable onSaveInstanceState() { // do not change the rest of the code Bitmap bitmap = source.readParcelable(getClass().getClassLoader()); picture = new BitmapDrawable(bitmap); } public void writeToParcel(Parcel out, int flags) { // do not change the rest of the code Bitmap bitmap = ((BitmapDrawable) picture).getBitmap(); out.writeParcelable(bitmap, flags); }
但错误依然存在,此时出现Drawable无法转为BitmapDrawable的问题。
我还尝试保存Drawable的ID,但无法在静态上下文使用getResources()方法:
picture = getResources().getDrawable(id)
请问是否有其他保存Drawable对象的可行方法?
可行解决方案
方案一:保存Drawable资源ID(推荐轻量化方案)
既然Drawable来自res/drawable目录,直接保存资源ID是最稳妥的方式,同时解决静态上下文无法调用getResources()的问题:
- 修改SavedState类,只保存资源ID和旋转角度:
private static class SavedState extends BaseSavedState { int drawableResId; float degree = 0f; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); degree = source.readFloat(); drawableResId = source.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeFloat(degree); out.writeInt(drawableResId); } 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]; } }; }
- 重写
onSaveInstanceState,保存Drawable对应的资源ID:
@Nullable @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState myState = new SavedState(superState); myState.degree = this.degree; // 获取VectorDrawable的资源ID if (this.picture instanceof VectorDrawable) { myState.drawableResId = ((VectorDrawable) this.picture).getConstantState().getResourceId(); } return myState; }
- 重写
onRestoreInstanceState,通过View自身的Resources重新加载Drawable:
@Override protected void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); this.degree = savedState.degree; // 利用View的上下文加载Drawable,兼容静态环境问题 this.picture = getResources().getDrawable(savedState.drawableResId, getContext().getTheme()); invalidate(); }
方案二:将VectorDrawable转为Bitmap保存
如果Drawable是动态修改过的(无法通过资源ID恢复),可以将其转为Bitmap保存,避免类型转换问题:
- 修改SavedState类,保存Bitmap对象:
private static class SavedState extends BaseSavedState { Bitmap drawableBitmap; float degree = 0f; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); degree = source.readFloat(); drawableBitmap = source.readParcelable(getClass().getClassLoader()); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeFloat(degree); out.writeParcelable(drawableBitmap, flags); } 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]; } }; }
- 实现Drawable转Bitmap的工具方法:
private Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } // 处理VectorDrawable等非BitmapDrawable类型 Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; }
- 重写状态保存与恢复方法:
@Nullable @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState myState = new SavedState(superState); myState.degree = this.degree; myState.drawableBitmap = drawableToBitmap(this.picture); return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); this.degree = savedState.degree; this.picture = new BitmapDrawable(getResources(), savedState.drawableBitmap); invalidate(); }
方案三:直接使用Parcelable兼容(API 29+)
从Android API 29开始,Drawable实现了Parcelable接口,你的环境是API 32可以直接使用,但需注意版本兼容性:
private static class SavedState extends BaseSavedState { Drawable picture; float degree = 0f; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); degree = source.readFloat(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { picture = source.readParcelable(getClass().getClassLoader(), Drawable.class); } } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeFloat(degree); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { out.writeParcelable(picture, flags); } } 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]; } }; }
此方案仅适用于API 29及以上版本,若需兼容更低版本,优先选择前两个方案。
内容的提问来源于stack exchange,提问作者sh_nadegda

