0

0

Android自定义视图:如何正确保存包含Drawable的状态

花韻仙語

花韻仙語

发布时间:2025-10-12 12:32:38

|

881人浏览过

|

来源于php中文网

原创

Android自定义视图:如何正确保存包含Drawable的状态

android自定义视图中,直接将drawable对象序列化为parcelable是不可行的,会导致classcastexception。本文将深入探讨这一问题,并提供有效的替代方案,指导开发者如何通过保存drawable的资源id或其他可序列化数据来正确地恢复视图状态,确保应用在配置变更后能保持一致的用户界面。

Drawable对象序列化的挑战

在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的尝试与失败

为了说明这一问题,我们来看一个典型的错误尝试。假设一个自定义视图需要保存一个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 CREATOR
            = new Parcelable.Creator() {
        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的资源ID

鉴于Drawable对象本身难以直接序列化,最安全和推荐的做法是不要保存Drawable对象本身,而是保存能够重建该Drawable对象所需的数据。对于从资源文件(如res/drawable)加载的Drawable,其资源ID(int类型)是重建它的关键信息。

Figstack
Figstack

一个基于 Web 的AI代码伴侣工具,可以帮助跨不同编程语言管理和解释代码。

下载

原理阐述

Android的Resources系统在应用启动时加载资源,并根据设备配置进行管理。Drawable对象通常是通过Context.getResources().getDrawable(int id)方法从资源ID创建的。这个资源ID是一个简单的整数,可以很容易地序列化。在恢复状态时,我们可以利用保存的资源ID和当前的Context来重新创建Drawable对象。

实现细节

  1. 在SavedState中保存资源ID: 将SavedState类中的Drawable字段替换为int类型的资源ID字段。
  2. 在onSaveInstanceState()中获取并保存ID: 在自定义视图中,当Drawable被设置时,如果它是从资源加载的,就应该将其资源ID存储在一个成员变量中,然后在onSaveInstanceState()中将这个ID保存到SavedState。
  3. 在onRestoreInstanceState()中重建Drawable: 从SavedState中读取资源ID,然后使用getContext().getResources().getDrawable(id, getContext().getTheme())(或ContextCompat.getDrawable(getContext(), id))方法重新创建Drawable对象。

示例代码:使用资源ID保存自定义视图状态

以下是修改后的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 CREATOR
                = new Parcelable.Creator() {
            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。

注意事项与最佳实践

  1. 非资源Drawable的处理: 如果你的Drawable不是从资源加载的(例如,它是动态创建的ShapeDrawable或GradientDrawable),那么保存资源ID的方法就不适用。在这种情况下,你需要保存创建该Drawable所需的所有参数(如颜色、形状类型、尺寸、渐变属性等),然后在恢复时根据这些参数重新构建Drawable。
  2. 保持SavedState轻量: Parcelable的目的是高效传输数据。避免在SavedState中保存大型或复杂的对象图。只保存恢复视图状态所必需的最小数据集。
  3. Context的可用性: 在onRestoreInstanceState中,View的Context是可用的,因此可以安全地调用getContext().getResources().getDrawable()。
  4. API兼容性: 使用ContextCompat.getDrawable(context, resId)可以确保在不同Android版本上的兼容性,因为它会自动处理旧版API中getDrawable方法的废弃问题,并支持主题(theme)属性。

总结

在Android自定义视图中保存包含Drawable的状态时,直接序列化Drawable对象是行不通的。核心思想是不要保存Drawable对象本身,而是保存能够重建Drawable所需的数据。对于从资源加载的Drawable,最有效和推荐的方法是保存其资源ID。通过这种方式,可以在配置变更后,利用资源系统重新创建Drawable,从而确保视图状态的正确恢复和应用界面的稳定性。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

844

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

742

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

740

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

400

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

431

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

C++ 高级模板编程与元编程
C++ 高级模板编程与元编程

本专题深入讲解 C++ 中的高级模板编程与元编程技术,涵盖模板特化、SFINAE、模板递归、类型萃取、编译时常量与计算、C++17 的折叠表达式与变长模板参数等。通过多个实际示例,帮助开发者掌握 如何利用 C++ 模板机制编写高效、可扩展的通用代码,并提升代码的灵活性与性能。

9

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.8万人学习

C# 教程
C# 教程

共94课时 | 7.4万人学习

Java 教程
Java 教程

共578课时 | 49.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号