
在android自定义视图中,直接将drawable对象序列化为parcelable是不可行的,会导致classcastexception。本文将深入探讨这一问题,并提供有效的替代方案,指导开发者如何通过保存drawable的资源id或其他可序列化数据来正确地恢复视图状态,确保应用在配置变更后能保持一致的用户界面。
在Android开发中,自定义视图经常需要保存其内部状态,以便在配置变更(如屏幕旋转)后能够恢复。Android提供了Parcelable接口,允许对象在进程间或配置变更时进行高效序列化和反序列化。通常,我们会创建一个继承自BaseSavedState的内部类来封装需要保存的视图状态。
然而,当尝试直接保存Drawable对象时,开发者会遇到一个常见的问题:Drawable类本身并未实现Parcelable接口,也不是所有Drawable的子类都可直接序列化。例如,VectorDrawable等特定类型的Drawable对象,它们通常依赖于Resources上下文来创建和渲染,直接将其写入Parcel会导致运行时错误,如java.lang.ClassCastException: android.graphics.drawable.VectorDrawable cannot be cast to android.os.Parcelable。
为了说明这一问题,我们来看一个典型的错误尝试。假设一个自定义视图需要保存一个Drawable对象和一个浮点数degree:
// 错误的SavedState实现示例
private static class SavedState extends BaseSavedState {
Drawable picture; // 问题所在:Drawable不是Parcelable
Float degree = 0f;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
degree = source.readFloat();
// 尝试读取Drawable,会抛出ClassCastException
picture = source.readParcelable(getClass().getClassLoader());
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeFloat(degree);
// 尝试写入Drawable,如果picture不是Parcelable,会抛出ClassCastException
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];
}
};
}当视图尝试通过onSaveInstanceState()和onRestoreInstanceState()方法保存和恢复状态时,如果picture是一个VectorDrawable,上述代码将在writeToParcel或readParcelable时引发ClassCastException。
尝试将Drawable转换为BitmapDrawable
有开发者可能会尝试将Drawable转换为BitmapDrawable,因为Bitmap实现了Parcelable接口。然而,这种方法并非总是有效:
// 错误的转换尝试
protected Parcelable onSaveInstanceState() {
// ... 其他代码
Bitmap bitmap = ((BitmapDrawable) picture).getBitmap(); // 问题:VectorDrawable不能直接转换为BitmapDrawable
out.writeParcelable(bitmap, flags);
// ... 其他代码
}如果原始的Drawable是VectorDrawable而不是BitmapDrawable,这种转换会再次导致ClassCastException。VectorDrawable和BitmapDrawable是Drawable的不同子类,它们之间没有直接的继承或转换关系。
鉴于Drawable对象本身难以直接序列化,最安全和推荐的做法是不要保存Drawable对象本身,而是保存能够重建该Drawable对象所需的数据。对于从资源文件(如res/drawable)加载的Drawable,其资源ID(int类型)是重建它的关键信息。
原理阐述
Android的Resources系统在应用启动时加载资源,并根据设备配置进行管理。Drawable对象通常是通过Context.getResources().getDrawable(int id)方法从资源ID创建的。这个资源ID是一个简单的整数,可以很容易地序列化。在恢复状态时,我们可以利用保存的资源ID和当前的Context来重新创建Drawable对象。
实现细节
以下是修改后的SavedState和自定义视图状态保存/恢复的示例代码:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; // 推荐使用ContextCompat兼容旧版本API
public class MyCustomView extends View {
private Drawable picture;
private float degree = 0f;
private int drawableResId = 0; // 保存Drawable的资源ID
public MyCustomView(Context context) {
super(context);
init(null);
}
public MyCustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
// 假设这里初始化了picture,例如从XML属性或默认资源加载
// 为了演示,我们手动设置一个默认的Drawable
// 注意:在实际应用中,你需要确保drawableResId在Drawable被设置时同步更新
setPictureFromResource(R.drawable.ic_launcher_foreground); // 假设这是一个VectorDrawable
// 其他初始化逻辑...
}
// 设置Drawable的方法,同时保存其资源ID
public void setPictureFromResource(int resId) {
this.drawableResId = resId;
this.picture = ContextCompat.getDrawable(getContext(), resId);
if (this.picture != null) {
this.picture.setBounds(0, 0, getWidth(), getHeight()); // 设置边界,根据实际需求调整
}
invalidate(); // 刷新视图
}
// 设置Drawable的方法,如果不是来自资源,则drawableResId可以设置为0
public void setPicture(Drawable drawable) {
this.picture = drawable;
this.drawableResId = 0; // 非资源Drawable不保存ID
if (this.picture != null) {
this.picture.setBounds(0, 0, getWidth(), getHeight());
}
invalidate();
}
public void setDegree(float degree) {
this.degree = degree;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.rotate(degree, getWidth() / 2f, getHeight() / 2f);
if (picture != null) {
picture.draw(canvas);
}
canvas.restore();
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState myState = new SavedState(superState);
myState.degree = this.degree;
myState.drawableResId = this.drawableResId; // 保存资源ID
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
// 检查状态类型,避免ClassCastException
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
this.degree = savedState.degree;
this.drawableResId = savedState.drawableResId;
// 根据保存的资源ID重新创建Drawable
if (this.drawableResId != 0) {
this.picture = ContextCompat.getDrawable(getContext(), this.drawableResId);
if (this.picture != null) {
this.picture.setBounds(0, 0, getWidth(), getHeight()); // 重新设置边界
}
} else {
this.picture = null; // 如果没有ID,则清除Drawable
}
invalidate(); // 刷新视图以显示恢复后的状态
}
private static class SavedState extends BaseSavedState {
int drawableResId; // 保存Drawable的资源ID
float degree = 0f;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
degree = source.readFloat();
drawableResId = source.readInt(); // 读取资源ID
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeFloat(degree);
out.writeInt(drawableResId); // 写入资源ID
}
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];
}
};
}
}在上述代码中,MyCustomView通过drawableResId字段来追踪当前Drawable的资源ID。在onSaveInstanceState中,这个ID被写入Parcel;在onRestoreInstanceState中,它被读取并用于通过ContextCompat.getDrawable()方法重新加载Drawable。
在Android自定义视图中保存包含Drawable的状态时,直接序列化Drawable对象是行不通的。核心思想是不要保存Drawable对象本身,而是保存能够重建Drawable所需的数据。对于从资源加载的Drawable,最有效和推荐的方法是保存其资源ID。通过这种方式,可以在配置变更后,利用资源系统重新创建Drawable,从而确保视图状态的正确恢复和应用界面的稳定性。
以上就是Android自定义视图:如何正确保存包含Drawable的状态的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号