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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

[Android] Settings - 1 -

2016. 9. 26. 04:39 | Posted by 솔웅


반응형

Settings

앱에는 사용자가 기능 선택을 할 수 있는 세팅 메뉴를 가지고 있는 경우가 많다. 예를 들어 어떤 앱은 notification 가능 여부를 사용자가 선택할 수 있도록 하기도 하고 클라우드에 동기화 되는 주가 등을 설정하도록 하기도 합니다.
이 세팅 메뉴를 넣으려면 안드로이드의 Preference API를 사용해서 만들어야 합니다.

Setting Design


세팅을 어떤 모양으로 개발하느냐 하는 것은 Settings design guide를 살펴 보세요.





Figure 1. Screenshots from the Android Messaging app's settings. Selecting an item defined by a Preference opens an interface to change the setting.


안드로이드의 메세지 앱의 세팅 화면 입니다. 아이템을 누르면 지정된 Preference 의 세팅 화면이 열립니다.



Overview



User Interface를 위해 View object를 사용하는 대신 Settings는 XML 파일에 정의하도록 하는 이미 만들어진 Preference 클래스의 여러가지 subclass 등을 사용해서 표현 합니다.
Preference 객체는 한개의 세팅에 대한 building block 입니다. 각각의 Preference는 리스의 한 아이템으로 나타나고 사용자에게 해당 UI를 제공해 사용자가 setting을 modify 할 수 있도록 합니다.
예를 들어 CheckBoxPreference 는 체크박스를 표시하는 리스트 아이템을 생성합니다. 그리고 ListPreference는 여러개 중에 하나를 선택할 수 있는 리스트와 함께 dialog를 여는 아이템을 만듭니다.

Preference는 key-value 의 쌍으로 이뤄져 있고 이를 default SharedPreference file에 세팅된 내용을 저장하도록 합니다.
사용자가 세팅을 바꾸면 시스템은 이 SharedPreference 파일의 해당 내용을 업데이트 합니다. 이 SharedPreferences 파일을 직접 접근해서 다루는 것은 세팅된 내용을 read 해야 할 때 입니다.

각 세팅에 대해 SharedPreferences에 저장된 값들은 아래 data type들로 이뤄져 있습니다.

    Boolean
    Float
    Int
    Long
    String
    String Set

여러분 앱의 세팅 UI는 Preference 객체를 사용해 만들어 졌기 때문에 (View 객체가 아니라) 특별한 Activity나 Fragment subclass를 사용해서 이 리스트 세팅을 표시해야 합니다.

- 안드로이드 버전 3.0 미만 (API level 10 미만) 에서는 액티비티를 반드시 PreferenceActivity 클래스의 extension으로 build 해야 합니다.
- 안드로이드 버전 3.0 이상 에서는 PreferenceFragment를 가지고 있는 traditional Activity를 사용해서 앱 세팅을 표시해야 합니다. 여러 그룹의 세팅이 있다면 큰 화면을 둘로 나눠 이를 표시하기 위해 PreferenceActivity를 사용할 수도 있습니다.

어떻게 PreferenceActivityPreferenceFragment를 세팅하느냐 하는것은 Creating a Preference Activity Using Preference Fragment 에 대해 설명할 때 다를 겁니다.



Preference



앱에서 사용되는 세팅은 Preference 클래스의 subclass 를 사용해서 표현합니다. 각각의 sub 클래스는 property들의 세트로 이뤄져 있어 세팅의 타이틀과 디폴트값 같은 것들을 특정할 수 있도록 합니다.
각각의 subclass는 각자 자신들의 특징을 나타내는 프로퍼티와 인터페이스들을 제공합니다. 예를 들어 figure 1은 메세징 앱의 세팅에서 화면을 캡쳐한 것입니다 세팅 화면에 있는 각 리스트 아이템은 각기 다른 Preference 객체들을 지원하고 있습니다.

일반적으로 사용하는 preference들 몇개를 보면

 
CheckBoxPreference
    Shows an item with a checkbox for a setting that is either enabled or disabled. The saved value is a boolean (true if it's checked).


ListPreference
    Opens a dialog with a list of radio buttons. The saved value can be any one of the supported value types (listed above).


EditTextPreference
    Opens a dialog with an EditText widget. The saved value is a String.
   
   
다른 subclass들과 거기에 속하는 프로퍼티들을 보시려면 Preference 클래스를 참조하세요.

물론 이렇게 제공되는 클래스들이 모든것들을 커버하지는 못합니다. 어떤 경우에는 다른 특정한 형태가 필요할 수 있습니다. 예를 들어 숫자나 날짜를 picking 하는 preference 클래스는 제공되지 않습니다. 그래서 여러분만의 Preference Subclass를 만들어야 될 수도 있습니다. 이것에 대해 알고 싶으시면 Building a Custom Preference 섹션을 보세요.



Defining Preferences in XML



runtime에 Preference object를 instantiate 할수도 있지만 세팅 리스트는 XML에 Preference objects 의 hierarchy 와 함께 정의 해 놔야 합니다.
settings의 collection을 정의하기 위해 XML 파일을 사용하는 것이 더 좋습니다. 왜냐하면 이렇게 하면 쉽게 읽을 수 있고 update 하기도 간단하기 때문이죠.
또한 세팅은 일반적으로 pre-determined 됩니다. 그리고 runtime 때 이 collection들을 수정할 수도 있습니다.

Preference subclass는 클래스 이름을 사용해서 XML element로 정의 될 수 있습니다. i.e. <CheckBoxPreference>
이 XML 파일은 res/xml 디렉토리에 저장해야만 합니다. 파일 이름은 마음대로 해도 되지만 일반적으로 preferences.xml 로 합니다.
대개 1개의 파일만 필요합니다. 왜냐하면 hierarchy의 branch 들은 PreferenceScreen 대신 nested instance들에 의해 정의되기 때문입니다.

Note : 세팅에 multi-pane layout을 만드려면 각 fragment당 별도의 XML 파일을 말들어야 합니다.

XML 파일의 root node는 반드시 element 여야 합니다. 이 element 안에 각각의 Preference를 추가 합니다. <PreferenceScreen> element 안에 추가한 각 child는 세팅 리스트안의 각 아이템으로 표시됩니다.




예제



<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:key="pref_sync"
        android:title="@string/pref_sync"
        android:summary="@string/pref_sync_summ"
        android:defaultValue="true" />
    <ListPreference
        android:dependency="pref_sync"
        android:key="pref_syncConnectionType"
        android:title="@string/pref_syncConnectionType"
        android:dialogTitle="@string/pref_syncConnectionType"
        android:entries="@array/pref_syncConnectionTypes_entries"
        android:entryValues="@array/pref_syncConnectionTypes_values"
        android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>




이 예제에서는 CheckBoxPreference ListPreference 가 있습니다. 각 아이템들은 아래의 3가지 attribute들을 포함하고 있습니다.


android:key
    This attribute is required for preferences that persist a data value. It specifies the unique key (a string) the system uses when saving this setting's value in the SharedPreferences.

    The only instances in which this attribute is not required is when the preference is a PreferenceCategory or PreferenceScreen, or the preference specifies an Intent to invoke (with an <intent> element) or a Fragment to display (with an android:fragment attribute).


android:title
    This provides a user-visible name for the setting.


android:defaultValue
    This specifies the initial value that the system should set in the SharedPreferences file. You should supply a default value for all settings.
   
attribute에 대한 정보를 얻으려면 Preference (and respective subclass) 문서를 참조하세요.

세팅 리스트가 10 아이템을 초과하면 세팅의 그룹별로 title을 추가하고 싶을 겁니다. 혹은 그룹을 별도의 화면에 포시하던가요. 이러한 기능은 아래 섹션을 보세요.



Creating Setting Groups




Figure 2. Setting categories with titles.
1. The category is specified by the <PreferenceCategory> element.
2. The title is specified with the android:title attribute.



10개 이상의 세팅 리스트가 있으면 사용자가 보기 어려워 질 겁니다. 이럴 경우 세팅 리스트를 그룹화 시키면 도움이 될 겁니다. 이렇게 세팅을 그룹화 시키려면 아래 두가지 방법 중 하나를 사용하시면 됩니다.

   
    Using titles
    Using subscreens

이 중 하나만 사용해도 되고 둘 다 사용해도 됩니다. 어떤 걸 사용할 것인지는 어떻게 구분할 지에 따라 결정됩니다. 자세한 것은 Android Design's Settings guide를 참조하세요.



Using titles



세팅내 그룹들 사이에 divider와 heading을 제공하려면 (figure 2) PreferenceCategory 안에 Preference 객체들을 각 그룹으로 묶어 놓습니다.



<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/pref_sms_storage_title"
        android:key="pref_key_storage_settings">
        <CheckBoxPreference
            android:key="pref_key_auto_delete"
            android:summary="@string/pref_summary_auto_delete"
            android:title="@string/pref_title_auto_delete"
            android:defaultValue="false"... />
        <Preference
            android:key="pref_key_sms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_sms_delete"... />
        <Preference
            android:key="pref_key_mms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_mms_delete" ... />
    </PreferenceCategory>
    ...
</PreferenceScreen>




Using subscreens



subscreen으로 세팅 그룹을 나누고 싶으면 (figure 3) PreferenceScreen 안에 Preference 객체들의 그룹을 묶어 놓습니다.



Figure 3. Setting subscreens. The <PreferenceScreen> element creates an item that, when selected, opens a separate list to display the nested settings.



예제



<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- opens a subscreen of settings -->
    <PreferenceScreen
        android:key="button_voicemail_category_key"
        android:title="@string/voicemail"
        android:persistent="false">
        <ListPreference
            android:key="button_voicemail_provider_key"
            android:title="@string/voicemail_provider" ... />
        <!-- opens another nested subscreen -->
        <PreferenceScreen
            android:key="button_voicemail_setting_key"
            android:title="@string/voicemail_settings"
            android:persistent="false">
            ...
        </PreferenceScreen>
        <RingtonePreference
            android:key="button_voicemail_ringtone_key"
            android:title="@string/voicemail_ringtone_title"
            android:ringtoneType="notification" ... />
        ...
    </PreferenceScreen>
    ...
</PreferenceScreen>




Using intents



어떤 경우는 preference item을 settings screen이 아니라 다른 activity에서 열기를 원할 때도 있을 수 있습니다.
예를 들어 웹페이지를 보기 위한 웹브라우저 같은 경우.
사용자가 preference item을 선택했을 때 Intent 를 invoke 하려면 <intent> element를 해당 <preference> element의 child로 추가 합니다.

예를 들어 웹 페이지를 열 경우 이렇게 합니다.



<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>



다음의 attribute들을 사용해서 implicit/explicit intent를 사용할 수 있습니다.


android:action
    The action to assign, as per the setAction() method.


android:data
    The data to assign, as per the setData() method.


android:mimeType
    The MIME type to assign, as per the setType() method.


android:targetClass
    The class part of the component name, as per the setComponent() method.


android:targetPackage
    The package part of the component name, as per the setComponent() method.

   
Creating a Preference Activity



settings를 activity 안에 표시하려면 PreferenceActivity class를 extend 합니다.
Preference objects 의 hierarchy에 기반해 settings 리스트를 표시하는 Activity class의 extension 입니다.
PreferenceActivity는 사용자가 변경을 했을 때 각 preference와 연관된 세팅들을 자동적으로 persist 시켜 줍니다.

Note : Android 3.0 이상이면 대신에 PreferenceFragment를 사용해야 합니다. 사용법은 Using Preference Fragments section에서 설명 됩니다.



기억해야 할 가장 중요한 부분은 onCreate() callback 동안 view들의 레이아웃을 로딩하지 않는다는 겁니다. 대신에 addPreferencesFromResource() 를 호출해서 XML 파일에 선언한 preference를 Activity에 추가하게 됩니다.
예를 들어 PreferenceActivity를 위한 아주 간단한 코드는 아래와 같습니다.



public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}



이 정도면 어떤 경우에는 충분할 겁니다. 왜냐하면 사용자가 preference를 수정하면 시스템은 이 변경된 값을 default SharedPreferences 파일에 저장을 할 것이기 때문입ㅂ니다.
이렇게 되면 다른 어플리케이션 컴포넌트들에서 이 사용자의 세팅값을 읽어서 사용할 수 있게 됩니다.
하지만 대부분의 경우에는 좀 더 코딩이 필요할 겁니다. 변경이 될 떄 listen 하기 위한 경우 일 텐데요. 이와 관련해서는 Reading Preferences section을 참조하세요.



Using Preference Fragments



Android 3.0 (API level11) 이상에서는 Preference objects의 리스트를 표시하기 위해 PreferenceFragment를 사용해야 합니다.
어느 activity에나 이 PreferenceFragment를 추가할 수 있습니다. (PreferenceActivity를 사용할 필요가 없습니다.)

onCreate() 메소드에서 addPreferencesFromResource() 를 사용하면 아주 간단하게 PreferenceFragment를 implement 할 수 있습니다.



예제



public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}



이렇게 한 후 이 fragment를 Activity에 추가하려면 다른 Fragment를 추가하는 것과 똑 같이 하시면 됩니다.




예제



public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}



Note : PreferenceFragment는 Context object를 가지고 있지 않습니다. 만약에 Context 객체가 필요하면 getActivity()를 call 하실 수 있습니다.
이 경우 주의해야 할 사항은 activity에 fragment가 attach됐을 때 사용해야 한다는 겁니다. attach 되지 않았을 경우나 lifecycle이 종료되서 detached 됐을 경우 getActivity() 는 null을 return 하게 됩니다.



Setting Default Values



생성된 preferences들은 앱에서 중요한 behavior들이 정의 돼 있을 겁니다. 그래서 관련된 SharedPreferences 파일을 default value들과 함께 initialize 시키는 것이 중요합니다.
이 작업은 사용자가 앱을 처음 open 할 떄 이뤄져야 합니다.

이를 위해 첫번째로 해야 할 것은 XML 파일에 각 Preference objects 마다 default value를 정해줘야 한다는 겁니다. 이 경우 android:defaultValue attribute를 사용합니다.
이 값은 어떤 data type도 될 수 있습니다.



예제



<!-- default value is a boolean -->
<CheckBoxPreference
    android:defaultValue="true"
    ... />

<!-- default value is a string -->
<ListPreference
    android:defaultValue="@string/pref_syncConnectionTypes_default"
    ... />

그런 다음 main activity의 onCreate() 메소드에서 setDefaultValues();를 호출합니다.



PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);



onCreate()에서 호출 할 때 앱에는 디폴트 세팅이 제대로 초기화 되어 있어야 합니다. 예를 들어 cellular network 에서 다운로드를 할 것인지 여부 같은 것들이 제대로 세팅 되어 있어야 할 겁니다.



이 메소드는 세개의 arguments를 가지고 있습니다.



    Your application Context.
    The resource ID for the preference XML file for which you want to set the default values.
    A boolean indicating whether the default values should be set more than once.


    false일 경우 시스템은 이 메소드가 이전에 한번도 호출되지 않았을 경우에만 디폴트 값들을 세팅합니다. (or the KEY_HAS_SET_DEFAULT_VALUES in the default value shared preferences file is false).

3번째 argument를 false로 세팅하면 사용자가 저장한 preference를 디폴트 값으로 overriding 하지 않고 reseting 함으로서 activity가 시작될 때마다 안전하게 호출하도록 할 수 있습니다.
이 값을 true로 설정하면 이전의 저장된 값이 디폴트 값으로 override 하게 됩니다.

반응형