태그 보관물: PreferenceFragment

v7 Support Preference 라이브러리 사용하기

구글의 안드로이드는 매년 새로운 버전이 나오면서 새로운 기능을 제공하는 API를 발표한다. 새로운 버전이다 보니 이전의 버전에서 새로운 기능 또는 UI 등을 사용하기 힘든 상황이다. 그래서 구글은 Android Support 라이브러리(Support 라이브러리)들을 제공해서 하위 버전에서도 상위 버전의 기능과 UI를 사용할 수 있게 지원한다. 그래서 안드로이드 개발자들은 기본 API와 더불어 Support 라이브러리를 살펴봐야 하는 이중고(?)를 겪게 있다. 이 글에서는 Support 라이브러리 중의 하나인 v7 버전의 preference 라이브러리를 사용하는 방법과 약간의 팁을 살펴보자.

preference_01
그림 1, Android SDK Manager 툴 일부

그림 1에서 Android Support Library는 23.1.1 버전이 설치된 것을 확인할 수 있다. 그래서 Support 라이브러리 버전은 v4든 v7이든 23.1.1 버전을 사용한다는 것도 알 수 있다. 이 라이브러리는 %android_home%\extras\android\support 폴더에서 확인할 수 있다.

1. 환경설정

그래들(Gradle) 빌드 파일(Build.gradle)에 아래와 같은 의존 라이브러리가 필요하다.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.android.support:support-v4:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:preference-v7:23.1.1'
}

2. Preference xml 파일 추가

안드로이드에서 설정 화면 역시 xml 파일로 구성한다. 다른 UI와 다르게 목록의 형태로만 구성할 수 있도록 되어 있다. 기존의 Preference는 내부에서 ListView를 사용하고 있지만, v7의 경우에는 RecyclerView를 사용한다. 그래서 그래들 빌드 파일의 의존성에 v7의 다른 라이브러리가 필요한 것이다. 그리고 아래와 같은 설정 파일은 app/res/xml/ 아래에 위치해야 한다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <android.support.v7.preference.PreferenceCategory android:title="카테고리1">
        <android.support.v7.preference.SwitchPreferenceCompat
            android:key="pf_folder_display_option"
            android:icon="@mipmap/ic_launcher"
            android:title="카테 타이틀 01"
            android:summary="summary"
            android:defaultValue="false" />

        <android.support.v7.preference.SwitchPreferenceCompat
            android:icon="@mipmap/ic_launcher"
            android:title="카테 타이틀 02"
            android:summary="summary"
            android:defaultValue="false" />

        <android.support.v7.preference.SwitchPreferenceCompat
            android:icon="@mipmap/ic_launcher"
            android:title="summary"
            android:summary="summary"
            android:defaultValue="true" />
    </android.support.v7.preference.PreferenceCategory>

    <android.support.v7.preference.PreferenceCategory android:title="카테고리2">
        <android.support.v7.preference.SwitchPreferenceCompat
            android:title="SwitchPreferenceCompat 타이틀"
            android:summary="SwitchPreferenceCompat Summary"
            android:icon="@mipmap/ic_launcher"
            android:defaultValue="true" />
    </android.support.v7.preference.PreferenceCategory>

    <android.support.v7.preference.PreferenceCategory android:title="카테고리3">
        <android.support.v7.preference.Preference
            android:title="타이틀 02"
            android:summary="Summary"
            />

        <android.support.v7.preference.Preference
            android:title="타이틀 03"
            android:summary="Summary"
            />

        <android.support.v7.preference.Preference
            android:title="@string/app_name"
            android:summary="Summary"
            />
    </android.support.v7.preference.PreferenceCategory>
</android.support.v7.preference.PreferenceScreen>

3. 액티비티(Activiy) 예제

예제 프로젝트의 SettingsActivity.java 소스는 아래와 같다. 이 소스는 간단하게 아래의 PreferenceFragmentCompat을 상속해서 구현하고 있는 클래스로 화면을 구성하는 소스이다.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment(), null).commit();
    }
}

4. SettingsFragment 예제

이 클래스는 PreferenceFragmentCompat를 상속해서 실제 Preference 화면을 구성하는 xml을 로딩하는 소스이다. 기존에 사용하던 PreferenceFragment에서는 onCreate() 메서드에서 xml을 로딩했지만, PreferenceFragmentCompat 에서는 onCreatePreferences() 메서드를 사용한다.

import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat;

public class SettingsFragment extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle bundle, String s) {
        addPreferencesFromResource(R.xml.settings);
    }
}

5. 스타일(styles.xml) 추가

위 과정이 끝나면 테마에 preference 테마를 추가할 필요가 있다. 아래의 수정된 styles.xml 에서 AppTheme의 preferenceTheme 요소의 값으로 @style/PreferenceThemeOverlay를 사용하고 있는 것을 알 수 있다.

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="preferenceTheme">@style/PreferenceLocalTheme</item>
    </style>
    <style name="PreferenceLocalTheme" parent="@style/PreferenceThemeOverlay" />
</resources>

이제 위 과정의 결과로 아래와 같은 멋진(?) 화면을 확인할 수 있다.

preference_02
그림 2, v7 Preference 라이브러리를 사용한 설정 화면

그림 2를 살펴보면, 롤리팝의 머터리얼 디자인 테마와 홀로 테마가 섞여 있다는 것을 알 수 있다. 그래서 조금 더 머터리얼 디자인의 모습을 가지도록 UI를 변경해 보자.

6. 스타일(style.xml) 변경

위 결과 화면을 보면, v7 Preference 기본 UI가 머터리얼(Material) 디자인이라고 보기에는 구리다. 그래서 스타일을 변경해서 UI를 개선하는 방법을 살펴보자.

6.1 android.support.v7.preference.PreferenceCategory의 Text 스타일 변경

아래와 같이 PreferenceCategory에 보여지는 TextView의 스타일을 변경한다.

<style name="ListSeparatorTextView">
  <item name="android:textSize">16sp</item>
  <!--item name="android:textStyle">bold</item-->
  <item name="android:textColor">@color/accent</item>
  <item name="android:paddingTop">16dp</item>
  <item name="android:layout_marginBottom">16dp</item>
</style>

6.2 Preference의 Title과 Summary 스타일 변경

이 Preference의 Title은 textAppearanceLarge를 style로 사용하고 있고, Summary는 textAppearanceMedium을 style로 사용하고 있다. 그래서 위 설정화면의 Title과 Summary가 크게 느껴진다. 그래서 이 두개의 스타일을 변경해 보자.

<style name="LargeText">
   <item name="android:textSize">16sp</item>
</style>
<style name="MediumText">
   <item name="android:textSize">11sp</item>
   <item name="android:typeface">sans</item>
</style>

위 과정으로 변경된 전체 styles.xml은 아래와 같다.

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="preferenceTheme">@style/PreferenceLocalTheme</item>
    </style>

    <style name="AppThemePreference" parent="AppTheme">
        <item name="android:listSeparatorTextViewStyle">@style/ListSeparatorTextView</item>
        <item name="android:textAppearanceLarge">@style/LargeText</item>
        <item name="android:textAppearanceMedium">@style/MediumText</item>
    </style>

    <style name="PreferenceLocalTheme" parent="@style/PreferenceThemeOverlay" />

    <style name="ListSeparatorTextView">
        <item name="android:textSize">16sp</item>
        <!--item name="android:textStyle">bold</item-->
        <item name="android:textColor">@color/colorPrimary</item>
        <item name="android:paddingTop">16dp</item>
        <item name="android:layout_marginBottom">16dp</item>
    </style>

    <style name="LargeText">
        <item name="android:textSize">16sp</item>
    </style>

    <style name="MediumText">
        <item name="android:textSize">11sp</item>
        <item name="android:typeface">sans</item>
    </style>

</resources>

위 스타일로 변경된 스타일의 화면은 다음과 같다.
preference_03

위 스타일에서 설정을 가지고 있는 액티비티는 AppThemePreference를 테마로 사용하면 된다. 이 테마안에 보면 android:textAppearanceLarge나 android:textAppearanceMedium이 테마 전역으로 사용하는 요소이기 때문에 이 테마를 사용하는데는 주의를 해야 한다.

PreferenceFragment에서 스크롤바 제어하기..

PreferenceFragment 클래스는 안드로이드 API 11에서 처음 소개되었고, 기존의 PreferenceActivity(보통 설정화면을 처리하기 위해서 사용한다)를 허니콤 이상의 버전에서 Fragment로 대체하기 위해서 추가되었다.

대부분 안드로이드 앱의 설정 부분을 살펴보면, 설정의 항목이 많은 경우 기본 스크롤바를 보게 된다. 이 스크롤바에 스타일을 주거나, 없애는 등의 작업을 할 수 있는 방법을 살펴보자. PreferenceFragment 클래스에서 화면에 설정항목들을 보여주기 위한 뷰로 ListView를 사용하고 있다. 그리고 기본 API에서는 ListView에 접근하는 메서드나 ListView의 메서드를 지원하지 않는다.

그래서 PreferenceFragment를 상속한 클래스에서 Reflection을 이용해서  ListView객체를 사용해서, 간단하게 PreferenceFragment의 스크롤바를 제거해보자.

아래의 AbstractPreferenceFragment는 여러 설정화면을 구현하는 클래스가 상속해서 사용하도록 만든 부모 클래스이다. 각 설정화면을 구현하는 클래스에서는 이 AbstractPreferenceFragment 클래스를 상속해서, disableScrollBar()만 호출하면 스크롤바를 제거할 수 있다.

public class AbstractPreferenceFragment extends PreferenceFragment {
	protected void disableScrollBar() {
		Field field = null;
		try {
			field = PreferenceFragment.class.getDeclaredField("mList");
			// 소스를 확인해 보니, 아이스크림 이후(허니콤은 소스를 공개하고 있지 않아서 모르겠지만 동일하지 않을까 예측) 부터
			// 계속 mList라는 변수명을 사용하고 있음
			field.setAccessible(true);
			((ListView) field.get(this)).setVerticalScrollBarEnabled(false);
		} catch (Exception e) {
		}
	}
}

이제, 위의 코드를 활용해서 ListView에 스크롤바를 트윅해서 스타일이나, 여러가지 변경을 할 수 있겠다.