2010年8月20日金曜日

Viewの状態を保存する

画面の向きが変わったときなどにViewが再作成されるが、
その時に状態を保存しておき、再作成後に復帰させる。

ActivityのonSaveInstanceState(Bundle) を使う方法は随所で解説されているが、カスタムView側で保存する方法もあったのでメモ。

状態の保存は、View.onSaveInstanceStateをオーバーライドして行う。
このメソッドは、状態を保存したParcelableオブジェクトを返す。

状態の復元は、View.onRestoreInstanceStateをオーバーライドして行う。
onSaveInstanceStateで保存したParcelableを引数で渡してくれるので、適宜復元する。

Parcelableオブジェクトは、Bundleでも動作するが、TextViewやAbsListViewではView.BaseSavedStateを継承して独自クラスを作っている。どれが推奨されているかは不明。

注意点としては、内部的に、ViewのIDをキーとしてParcelableが保存されるので、
ViewのIDが無いと保存できないことと、必ず一意にしておくこと。
もしIDが重複していると、最後に実行したonSaveInstanceStateの情報しか保存されない。

これはスタイル属性全般の注意点だが、カスタムViewを作成した時に、
コンストラクタで渡されたAttributeSetをそのまま子ビューに渡すと、スタイル属性が全て子ビューに渡ってしまう。
そのため、もし放置しておくと、自分自身と子Viewの全てが同じIDを持つことになってしまう。
この状態になると、当然だがonSaveInstanceStateはマトモに機能しない。

このスタイル属性からのID重複で丸一日ハマった・・・。

サンプルコードはこんな感じ。ソースをまんま貼り付けると、えらく間延びするなぁ。


    /**
     * 画面の向きの変更時など、オブジェクトが再作成される時に状態を保存する
     * @see android.view.View#onSaveInstanceState()
     */
    @Override
    protected Parcelable onSaveInstanceState()
    {
        Parcelable parent = super.onSaveInstanceState();

        /*
         * BaseSavedState拡張版
         */
        {
            SavedState saved = new SavedState(parent);

            saved.first = 1;
            saved.second = "Test";
            saved.third = true;

            return saved;
        }

        /*
         * Bundle版。クラス実装が不要なので手軽?
         */
//        {
//            Bundle b = new Bundle();
//
//            b.putParcelable("Parent", parent);
//            b.putString("myData", "myData");
//
//            return b;
//        }
    }
    /**
     * onSaveInstanceStateで保存した状態を復元する
     * @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state)
    {
        /*
         * BaseSavedState拡張版の復元
         */
        {
            if(!(state instanceof SavedState))
                return;

            SavedState saved = (SavedState)state;

            super.onRestoreInstanceState(saved.getSuperState());

            int first = saved.first;
            String second = saved.second;
            boolean third = saved.third;
        }

        /*
         * Bundle版の復元
         */
//        {
//            if(!(state instanceof Bundle))
//                return;
//
//            Bundle b = (Bundle)state;
//
//            super.onRestoreInstanceState(b.getParcelable("Parent"));
//            String s = b.getString("myData");
//        }
    }

    /**
     * onSaveInstanceStateで保存する状態を保持するためのクラス
     */
    public static class SavedState extends View.BaseSavedState
    {
        private int first;
        private String second;
        private boolean third;

        /**
         * Parcelから状態を復元するためのコンストラクタ
         */
        public SavedState(Parcel in)
        {
            /*
             * 必ず書いた順序で読み込むこと!
             */
            super(in);

            first = in.readInt();
            second = in.readString();
            third = in.readInt() == 0 ? false : true;
        }

        /**
         * 状態の保存用コンストラクタ
         */
        public SavedState(Parcelable superState)
        {
            super(superState);
        }

        /**
         * Parcelにデータを書き込む
         * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
         */
        @Override
        public void writeToParcel(Parcel out, int flags)
        {
            super.writeToParcel(out, flags);

            out.writeInt(first);
            out.writeString(second);

            // booleanはそのまま書けないので、intなどに変換する
            out.writeInt(third ? 1 : 0);
        }

        /**
         * Parcelableの仕様で、ファクトリの実装が必要
         */
        public static final Parcelable.Creator CREATOR =
            new Parcelable.Creator()
        {
            public SavedState createFromParcel(Parcel source)
            {
                return new SavedState(source);
            }

            public SavedState[] newArray(int size)
            {
                return new SavedState[size];
            }
        };
    } 



0 件のコメント:

コメントを投稿