You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android自定义View:如何让Drawable在屏幕旋转后保留?

这确实是Android自定义View开发里挺头疼的一个问题——配置变更(比如屏幕旋转)时,Activity重建导致自定义View的Drawable实例没法直接保存,毕竟通用Drawable既不实现Parcelable也不支持Serializable。我来分享几个实战中验证过的解决方案:

解决方案一:优先记录资源ID,用Bitmap兜底保存Drawable实例

核心思路是区分用户设置Drawable的方式:如果是通过资源ID设置的,就直接保存ID;如果是传入的Drawable实例,就把它转换成可Parcelable的Bitmap来保存。

具体实现步骤:

  1. 在自定义View中维护两个状态变量:

    private int mDrawableResId = -1; // 标记资源ID,-1表示未设置
    private Bitmap mDrawableBitmap; // 保存Drawable转成的Bitmap
    
  2. 提供两个重载的设置Drawable方法,分别处理资源ID和实例:

    public void setDrawable(int resId) {
        mDrawableResId = resId;
        mDrawableBitmap = null;
        updateViewDrawable();
    }
    
    public void setDrawable(Drawable drawable) {
        mDrawableResId = -1;
        if (drawable != null) {
            mDrawableBitmap = drawableToBitmap(drawable);
        } else {
            mDrawableBitmap = null;
        }
        updateViewDrawable();
    }
    
  3. 实现Drawable转Bitmap的工具方法:

    private Bitmap drawableToBitmap(Drawable drawable) {
        // 如果是BitmapDrawable直接取Bitmap
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        // 其他Drawable类型,创建对应尺寸的Bitmap并绘制
        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;
    }
    
  4. 重写状态保存与恢复方法:

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        // 先保存父类的状态
        bundle.putParcelable("super_state", super.onSaveInstanceState());
        bundle.putInt("drawable_res_id", mDrawableResId);
        bundle.putParcelable("drawable_bitmap", mDrawableBitmap);
        return bundle;
    }
    
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            mDrawableResId = bundle.getInt("drawable_res_id");
            mDrawableBitmap = bundle.getParcelable("drawable_bitmap");
            // 恢复父类状态
            super.onRestoreInstanceState(bundle.getParcelable("super_state"));
            updateViewDrawable();
            return;
        }
        super.onRestoreInstanceState(state);
    }
    
  5. 实现更新View Drawable的方法:

    private void updateViewDrawable() {
        Drawable targetDrawable = null;
        if (mDrawableResId != -1) {
            targetDrawable = getContext().getResources().getDrawable(mDrawableResId);
        } else if (mDrawableBitmap != null) {
            targetDrawable = new BitmapDrawable(getContext().getResources(), mDrawableBitmap);
        }
        // 根据你的View需求设置Drawable,比如作为背景或前景
        setBackground(targetDrawable);
    }
    

注意:这个方法对VectorDrawable也适用,但要注意低版本API(<21)的兼容处理;如果Drawable尺寸很大,保存Bitmap可能会占用较多内存,需要根据场景权衡。

解决方案二:用ViewModel保存Drawable实例

ViewModel的生命周期不受配置变更影响,所以可以把Drawable实例托管给ViewModel,避免随Activity重建丢失。

具体实现步骤:

  1. 创建一个自定义ViewModel类:

    public class CustomViewViewModel extends ViewModel {
        // 用弱引用避免内存泄漏
        private WeakReference<Drawable> mDrawableRef;
    
        public void setDrawable(Drawable drawable) {
            mDrawableRef = new WeakReference<>(drawable);
        }
    
        public Drawable getDrawable() {
            return mDrawableRef != null ? mDrawableRef.get() : null;
        }
    }
    
  2. 在Activity/Fragment中绑定ViewModel,并同步Drawable状态:

    // 获取ViewModel
    CustomViewViewModel viewModel = new ViewModelProvider(this).get(CustomViewViewModel.class);
    // 设置Drawable时同步到ViewModel
    Drawable myCustomDrawable = ...;
    myCustomView.setDrawable(myCustomDrawable);
    viewModel.setDrawable(myCustomDrawable);
    
  3. 在自定义View中恢复Drawable:

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 从ViewModel中获取Drawable(要求Context是ViewModelStoreOwner,比如Activity/Fragment)
        CustomViewViewModel viewModel = new ViewModelProvider((ViewModelStoreOwner) getContext())
                .get(CustomViewViewModel.class);
        Drawable savedDrawable = viewModel.getDrawable();
        if (savedDrawable != null) {
            setDrawable(savedDrawable);
        }
    }
    

注意:这个方法仅适用于View所在的Context是ViewModelStoreOwner的场景(比如Activity或Fragment);用弱引用是为了避免Drawable持有Context导致内存泄漏。

解决方案三:AndroidX SavedStateHandle持久化状态

如果你使用AndroidX,可以结合SavedStateHandle来保存状态,它既拥有ViewModel的生命周期优势,又能在进程被杀死后恢复状态,同样需要把Drawable转成Bitmap保存。

具体实现步骤:

  1. 创建带SavedStateHandle的ViewModel:

    public class CustomViewSavedStateViewModel extends ViewModel {
        private final SavedStateHandle mSavedStateHandle;
        private static final String KEY_RES_ID = "drawable_res_id";
        private static final String KEY_BITMAP = "drawable_bitmap";
    
        public CustomViewSavedStateViewModel(SavedStateHandle savedStateHandle) {
            mSavedStateHandle = savedStateHandle;
        }
    
        public void saveDrawableState(int resId, Bitmap bitmap) {
            mSavedStateHandle.set(KEY_RES_ID, resId);
            mSavedStateHandle.set(KEY_BITMAP, bitmap);
        }
    
        public int getDrawableResId() {
            return mSavedStateHandle.get(KEY_RES_ID, -1);
        }
    
        public Bitmap getDrawableBitmap() {
            return mSavedStateHandle.get(KEY_BITMAP);
        }
    }
    
  2. 在自定义View中使用这个ViewModel来保存和恢复状态,逻辑和方案一类似,只是把状态存储从View的Bundle转移到SavedStateHandle中即可。

方案选择建议
  • 如果你的View大多通过资源ID设置Drawable,优先选方案一,简单直接兼容性好;
  • 如果View和Activity/Fragment强绑定,方案二更优雅,避免Bitmap转换的内存开销;
  • 追求现代Android开发最佳实践,方案三是AndroidX推荐的方式,兼顾生命周期和持久化能力。

内容的提问来源于stack exchange,提问作者Roberto Leinardi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:53:33