9

質問

XMLで定義されたウィジェットレイアウトを使用して、個々のウィジェットインスタンスのコンポーネントがすべて同じIDを持っている場合、ビューウィジェットインスタンスの状態をどのように保存しますか?

たとえば、NumberPickerウィジェットで使用されているウィジェットを取り上げます( SDKに公開されていないことにTimePicker注意してください)。これは、 1つのインクリメントボタン、1つのデクリメントボタン、および数値を直接入力できる1NumberPickerつのコンポーネントから拡張される3つのコンポーネントを備えたシンプルなウィジェットです。コードがこれらのウィジェットと相互作用するために、それらはすべてID(、および)を持っています。number_picker.xmlEditTextR.id.incrementR.id.decrementR.id.timepicker_input

NumberPickerXMLレイアウトに3つのがあり、それらに個別のID(たとえばR.id.hourR.id.minute)を指定するとします。¹このレイアウトは、アクティビティのコンテンツビューに拡張されます。アクティビティの方向を変更することにしたのでActivity.onSaveInstanceState(Bundle)、IDを持つ各ビューのビューステートを保存すると便利です(これはデフォルトの動作です)。

残念ながら、3つNumberPickerのにはEditTextすべて同じIDを共有するがあります— R.id.timepicker_input。したがって、アクティビティが復元されると、ビュー階層の最下位にあるのは、3つすべての状態が保持されているように見えるアクティビティです。さらに、NumberPicker保存時にフォーカスがあったかどうかに関係なく、復元するとフォーカスが最初に移動します。

TimePicker状態自体を個別に保存することで、この問題を回避します。残念ながら、これは多くの作業なしではカーソル位置またはフォーカスされたビューを保持しません。それがどのようにその状態を維持するのかはわかりません(そして、時間入力ダイアログですばやく遊んでいると、どういうわけかそれが可能であることが示されているようです)。

この問題を示すサンプルコードをご覧ください: https ://github.com/xxv/AndroidNumberPickerBug


¹ビュー階層では、これにより、IDに拡張されるのIDが設定LinearLayoutNumberPickerれます。

4

5 に答える 5

17

独自の複合ビューを作成しようとしたときに、同じ問題に遭遇しました。Androidのソースコードを見ると、複合ビューを実装する正しい方法は、複合ビュー自体が子のインスタンス状態を保存および復元する責任を負い、インスタンス状態を保存および復元する呼び出しが行われないようにすることだと思います。子ビューに渡されます。これは、アクティビティに同じ複合ビューのインスタンスが複数ある場合に、子ビューのIDが一意にならないという問題を回避します。

これは複雑に聞こえるかもしれませんが、それは本当に非常に簡単であり、APIは実際にこの正確なシナリオをプロビジョニングします。これがどのように行われるかについてブログ投稿をここに書きましたが、基本的に複合ビューでは、特定の要件を満たすためにonSaveInstanceState()とonRestoreInstanceState()をカスタマイズする次の4つのメソッドを実装する必要があります。

@Override
protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    return new SavedState(superState, numberPicker1.getValue(), numberPicker2.getValue(), numberPicker3.getValue());
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    SavedState savedState = (SavedState) state;
    super.onRestoreInstanceState(savedState.getSuperState());

    numberPicker1.setValue(savedState.getNumber1());
    numberPicker2.setValue(savedState.getNumber2());
    numberPicker3.setValue(savedState.getNumber3());
}

@Override
protected void dispatchSaveInstanceState(SparseArray container) {
    // As we save our own instance state, ensure our children don't save 
    // and restore their state as well.
    super.dispatchFreezeSelfOnly(container);
}

@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
    /** See comment in {@link #dispatchSaveInstanceState(android.util.SparseArray)} */
    super.dispatchThawSelfOnly(container);
}

NumberPicker / TimePickerの問題に関しては、別のコメントで述べられているように、NumberPickerとTimePickerにバグがあるようです。これを修正するには、両方をオーバーライドして、私が説明したソリューションを実装します。

于 2012-03-03T18:13:04.830 に答える
0

私はゲームに少し遅れていますが、私は自分の意見を述べたかったのです。

を使用して、android:tagそれぞれをグループ化できますView。したがって、各状態を「リロード」できます。

Android Developer Webサイトから:

android:tag

文字列を含むこのビューのタグを指定します。後でView.getTag()で取得するか、View.findViewWithTag()で検索します。

于 2011-11-16T21:17:30.097 に答える
0

3つのNumberPickerを使用した複合ビューでまったく同じ問題が発生しました。別のサイトで、timepicker_inputのIDを一意のランダムIDに再割り当てすることを含む解決策を見つけました。これは機能しますが、新しいIDが選択されると、そのIDは構成の変更後も保持される必要があるため、複雑になります。そのため、追加のコードが必要になります。

私のアプリケーションでは、そしておそらく他の99%では、はるかに単純なアプローチ(ハック)が機能します。Idsの名前空間には65536個の一意の識別子(0x7f090000から0x7f09ffff)の余地があることに気付きましたが、私のアプリケーションは20程度しか使用しておらず、最初から単調に割り当てられています。さらに、NumberPickerウィジェット自体には、ツリー内に一意の識別子があります(たとえば、元の投稿のように、R.id.hour、R.id.minute、およびR.id.second)。私の解決策は、EditTextウィジェットのIDをNumberPickerウィジェットのIDとオフセットに再割り当てすることです。

これは、NumberPickerコードへの1行の変更です。単に追加します:

mText.setId(getId() + 1000);

NumberPicker.javaの次の行の後:

mText = (EditText) findViewById(R.id.timepicker_input);

もちろん、オフセットはアプリケーションの要件に基づいて調整できます。

同じアプローチが他の複合ウィジェットでも機能すると思います。

上記の例では、このアプローチにより、個々のEditTextウィジェットの状態を保存および復元でき、フォーカスされたビューも保存できます。

于 2011-06-08T21:50:55.923 に答える
0

単一の整数で構成される状態の説明を持つ複合ウィジェットがありmValue
ます。次の2つのメソッドを次のようにオーバーライドします。

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if(getId() != NO_ID) {
        Parcel p = Parcel.obtain();
        p.writeInt(mValue);
        p.setDataPosition(0);
        container.put(getId(), new MyParcelable(p));
    }
}

@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    if(getId() != NO_ID) {
        Parcelable p = container.get(getId());
        if(p != null && p instanceof MyParcelable) {
            MyParcelable mp = (MyParcelable) p;
            updateAllValues(mp.getValue());
        }
    }
}

ではdispatchSaveInstanceState()、状態をシリアル化しています。より複雑な状態には、より複雑なストレージ構造が必要になります。 正しく
確認してください。ウィジェットに小包のタグとして使用しているIDが割り当てられている場合にのみ、状態を保存しますsetDataPosition()

ではdispatchRestoreInstanceState()、小包を開梱しています。
ウィジェットにIDが割り当てられていて、コンテナにこのIDと一致するタグが付いた小包が含まれている場合にのみ、解凍を実行します。
またParcelable、適切なクラスである必要があります。
より複雑なウィジェットの場合、解凍はより複雑になります。

必要な最後のコンポーネントはParcelable、次のようなサブクラスです。

static class MyParcelable implements Parcelable {

    private int mValue;

    private MyParcelable(Parcel in) {
        mValue = in.readInt();
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mValue);
    }

    int getValue() {
        return mValue;
    }

    public static final Parcelable.Creator<MyParcelable> CREATOR
        = new Parcelable.Creator<MyParcelable>() {

            public MyParcelable createFromParcel(Parcel source) {
                return new MyParcelable(source);
            }

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

}

このフレームワークの使用は、フラグメントの操作や構成の管理よりもはるかに簡単です。

于 2012-01-12T23:22:31.797 に答える
-4

非常に単純です:あなたはしません。マニフェストファイルで方向変更のがらくたを無効にしてください。ビューステート保存メカニズムには本質的に欠陥があり、彼らは単にこれを考え抜かなかった。

状態を維持したい場合は、単一のアクティビティ内でIDを再利用することはできません。これは基本的に、単一のレイアウトを複数回使用できないことを意味します。これにより、TimePickerのようなより複雑なウィジェットを基本的に適切に実行できなくなります。

オーバーライドしてハッキングすることで、子供たちに自分の状態を維持させることができますがdispatchSaveInstanceState、自分で管理する以外に、集中力を維持する方法も見つかりませんでした。

APIを壊さずに状態スコープを作成することでこれを修正できると思いますが、息を止めないでください。

于 2011-03-11T12:33:51.633 に答える