반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

[Android] Settings - 3 -

2016. 10. 13. 05:18 | Posted by 솔웅


반응형

Building a Custom Preference

안드로이드 프레임워크는 여러가지 모양으로 세팅 화면을 꾸밀 수 있도록 다양한 Preference subclass들을 제공합니다. 하지만 어떤 경우 이런 built-in solution 이 아닌 다른 모양으로 꾸며야 될 때가 있습니다. 예를 들어 number picker 나 date picker 등이 있습니다. 이런 경우 custom preference를 만들 수 있는데요. pPreference 클래스나 다른 subclass 를 extend 해서 사용하면 됩니다.

Preference class를 extend 할 때 주의해야 할 점 몇가지가 있습니다.

    사용자가 세팅을 클릭했을 때 보여 질 UI를 정합니다.
    적당한 때에 세팅 값들을 저장합니다.
    Preference를 view에 값들이 나올 때 Initialize 해 줍니다.
    시스템에 의해 request 될 때는 default 값을 줍니다.
    Preference 가 자신의 UI (dialog 같은)를 제공할 때 lifecycle 변화를 컨트롤 하기 위해 state를 저장하고 restore 합니다. (예를 들어 사용자가 스크린을 rotate 할 때)

   
아래 섹션들에서 이른 일들을 하려면 어떻게 해야 하는지를 설명 할 겁니다.


Specifying the user interface


만약 Preference class 를 direct로 extend 한다면 사용자가 해당 아이템을 선택 했을 때 어떤 action이 일어나도록 하기 위해 onClick() 메소드를 implemet 해야 합니다. 대부분의 custom settings는 DialogPreference를 extend 합니다. dialog를 보여야 하니까요. 그러면 일의 진행을 좀 더 간단하게 처리할 수 있죠. DialogPreference를 extend 할 때 그 클래스에서 layout을 생성하는 동안 setDialogLayoutResource()를 호출해야 합니다.


예를 들어 여기 custom DialogPreference 를 위한 constructor 가 있습니다. layout을 정의하고 positive, negative 버튼을 정의 합니다.


public class NumberPickerPreference extends DialogPreference {
    public NumberPickerPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setDialogLayoutResource(R.layout.numberpicker_dialog);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);

        setDialogIcon(null);
    }
    ...
}



Saving the setting's value




세팅에 대한 value들은 Preference class의 persist*() 메소드를 호출하면 어느때든지 저장할 수 있습니다. 예를 들어 값이 Integer 일 경우는 persistInt()를 Boolean 일 경우는 persistBoolean()을 호출하면 됩니다.


Note:  각각의 Preference 는 한가지의 data type으로 정의 됩니다. 그리고 거기에 해당하는 persist*() 메소드를 사용하여야 합니다.


persist를 선택하면 세팅은 extend 한 Preference class에 따라 진행 됩니다. DialogPreference 를 extend 했다면 사용자가 OK 버튼을 눌러서 이 창이 닫힐 때 해당 값을 persist 해야 합니다.


DialogPreference 가 닫힐 때, 시스템은 onDialogClosed() 메소드를 호출합니다. 이 메소드는 boolean argument를 가지고 있습니다. 사용자가 선택한 것이 OK 인지 Cancel 인지를 구분하기 위한 것이죠.
OK 를 누르면 해당 값을 persist 합니다.


@Override
protected void onDialogClosed(boolean positiveResult) {
    // When the user selects "OK", persist the new value
    if (positiveResult) {
        persistInt(mNewValue);
    }
}


이 예제에서는 mNewValue가 class member 입니다. 세팅의 현재 선택 된 값을 가지고 있는 변수죠. persistInt를 호출해서 이 값을 SharedPreference file 에 저장합니다.
(automatically using the key that's specified in the XML file for this Preference).


Initializing the current value

시스템이 화면에 여러분의  Preference를 추가 할 때 onSetInitialValue() 메소드가 호출됩니다. 이 메소드는 이 세팅이 persisted value 인지 여부를 notify 하게 됩니다. 만약 persisted value가 없으면 default value를 제공합니다.

onSetInitialValue() method는 boolean, restorePersistedValue 를 pass 합니다. 세팅에 값이 persisted 됐는지 여부를 알려주죠. true 이면 해당 Preference class의 getPersisted*() 메소드를 호출해서 persisted 된 값을 retrieve 해야 합니다. Integer value 인 경우는 getPersistedInt() 가 되겠죠. 최신의 값을 화면에 표시하려면 제 때애 UI를 update 해 줘야 합니다.

만약 restorePersistedValue가 false 이면 두번째 argument로 전달 된 default value를 사용해야 합니다.


@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // Restore existing state
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // Set default state from the XML attribute
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}


각각의 getPersisted*() 메소드는 persist 값이 없거나 key 가 존재하지 않을 경우 사용될 default 값을 받게 됩니다. 위 예제에서 local constant는 getPersistedInt()가 persisted 값을 return 하지 못학 경우 사용될 default 값을 지정해 놓았습니다.

Caution : getPersisted*() 메소드에서 defaultValue를 default 값으로 사용할 수 없습니다. 왜냐하면 restorePersistedValue 가 true 이면 이 갑은 항상 null 일 것이기 때문입니다.



Providing a default value


당신의 Preference class 의 instance 가 default value를 가리키고 있다면 (with the android:defaultValue attribute) 시스템은 값을 가져오기 위해 해당 object를 instantiate할 때 onGetDefaultValue() 를 호출할 것입니다.  이 메스드를 반드시 implement 해서 시스템이 이 default value를 SharedPreferences 파일에 저장할 수 있도록 해야 합니다.


@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, DEFAULT_VALUE);
}


이 method argument들은 여러분이 필요로 하는 모든 것들을 제공 합니다. : the array of attributes and the index position of the android:defaultValue, 여러분이 반드시 retrieve 해야 할 것들이죠. 이 메소드를 attribute에서 디폴트 값을 추출하기 위해 반드시 implement 해야 하는 이유는 해당 값이 undefined 됐을 경우 attribute에 대해 local default 값을 specify 해야만 하기 때문입니다.



Saving and restoring the Preference's state


layout 의 View 처럼, 여러분의 Preference subclass는 액티비티나 fragment 가 restart 되는 경우 그 상태를 save 하거나 restoring 해야 하기 때문입니다. (사용자가 화면을 rotate 시켰을 경우). Preference class의 상태를 정확하게 저장하거나 restore 하기 위해서는 lifecycle callback 메소드인 onSaveInstanceState() and onRestoreInstanceState()를 호출 해야만 합니다.

Preference의 state는 Parcelable interface 를 implement 한 object 에 의해 정의 됩니다. 안드로이드 프레임워크는 여러분의 state object를 정의하기 위한 starting point로서 이런 object를 제공합니다.

어떻게 이 Preference class 가 그 상태를 저장하는지 정의하기 위해 Preference.BaseSavedState class 를 extend 해야 합니다. 그리고 몇개의 메소드들을 override 하고 CREATOR 객체를 정의합니다.

대부분의 앱들은, 아래와 같이 implementation을 사용할 수 있습니다. Integer 가 아닌 경우 아래 에제에서 몇 줄만 바꿔서 사용하시면 됩니다.



private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readInt();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeInt(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    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];
        }
    };
}

Preference.BaseSavedState implementation을 여러분 앱에 추가 했다면 (usually as a subclass of your Preference subclass), 여러분은 Preference subclass에 대해 onSaveInstanceState() and onRestoreInstanceState()를 implement 합니다.


For example:


@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    if (isPersistent()) {
        // No need to save instance state since it's persistent,
        // use superclass state
        return superState;
    }

    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current
    // setting value
    myState.value = mNewValue;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }

    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());

    // Set this Preference's widget to reflect the restored state
    mNumberPicker.setValue(myState.value);
}

반응형