PreferenceFragmentCompat の PreferenceCategory の下側マージンが太すぎる件

android.preference.PreferenceFragmentAndroid P から非推奨になります。android.support.v7.preference.PreferenceFragmentCompat を使ってください。

This class was deprecated in API level P.
Use PreferenceFragmentCompat
PreferenceFragment | Android Developers

PreferenceFragmentCompat でググると面倒くさそうな記事がいくつも出てくるから嫌なんだけど…。

仕方ないので使ってみることにします。

PreferenceFragmentCompat の導入

モジュールの build.gradle に依存関係を追加。

dependencies {
    //...
    implementation 'com.android.support:preference-v7:27.1.0'
}

適当な PreferenceScreen を作る。(preferences.xml)

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="設定カテゴリ 1">
        <SwitchPreference android:title="スイッチ" />
        <CheckBoxPreference android:title="チェックボックス" />
    </PreferenceCategory>
    <PreferenceCategory android:title="設定カテゴリ 2">
        <Preference android:title="何か 1" />
        <Preference android:title="何か 2" />
    </PreferenceCategory>
</PreferenceScreen>

PreferenceFragmentCompat を継承したクラスを作る。

public class MyPreferenceFragmentCompat extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        addPreferencesFromResource(R.xml.preferences);
    }
}

Activity で読み込む。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

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

とりあえず実行してみる。

しかしアプリはすぐに落ちて、Logcat を見ると preferenceTheme を指定しろと言われている。

java.lang.IllegalStateException: Must specify preferenceTheme in theme

styles.xml を編集する。@style/PreferenceThemeOverlay.v14.Material を指定するとマテリアルデザインになるらしい。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- ... -->

        <!-- PreferenceFragmentCompat のための設定 -->
        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
    </style>
</resources>

再び実行。

f:id:Ginkyo:20180308191452p:plain

今度は落ちなかったけど、よく見るとなんか変。

比較用に android.preference.PreferenceFragment を使った物も用意した。左側が PreferenceFragmentCompat で、右側が android.preference.PreferenceFragment

f:id:Ginkyo:20180308192024p:plainf:id:Ginkyo:20180308192040p:plain

PreferenceCategory に余計なマージンが付いているのが分かる。

原因は preferenceTheme に指定した @style/PreferenceThemeOverlay.v14.Material だと思うので、それを調べてみる。

<style name="PreferenceThemeOverlay.v14.Material">
    <!-- ... -->
    <item name="preferenceCategoryStyle">@style/Preference.Category.Material</item>
    <!-- ... -->
</style>
<style name="Preference.Category.Material">
    <item name="android:layout">@layout/preference_category_material</item>
</style>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dip"
    android:textAppearance="@style/Preference_TextAppearanceMaterialBody2"
    android:textColor="@color/preference_fallback_accent_color"
    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
    android:paddingTop="16dip" />

android:layout_marginBottom の指定がある。

android:layout_marginBottom="16dip"

恐らく原因はこれ。原因が分かったので自力で直せるだろうが、楽チンな方法がないか調べてみる。

同じような悩みを抱えた人がいた。

github.com

Android-Support-Preference-V7-Fix というライブラリに挙げられた Issue で、返信を見ると解決済みらしい。

Android-Support-Preference-V7-Fix の導入と置き換え

とりあえず導入してみる。

github.com

com.android.support:preference-v7 の代わりに com.takisoft.fix:preference-v7 を追加する。

dependencies {
    //...

    //implementation 'com.android.support:preference-v7:27.1.0'
    implementation 'com.takisoft.fix:preference-v7:27.1.0.0'
}

Java コードの import を書き換えて、オーバーライドするメソッドも変更する。

//...
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat;

public class MyPreferenceFragmentCompat extends PreferenceFragmentCompat {
//    @Override
//    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
//        addPreferencesFromResource(R.xml.preferences);
//    }

    @Override
    public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
        addPreferencesFromResource(R.xml.preferences);
    }
}

preferenceCategory_marginBottom に 0dp を指定する。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- ... -->

        <!-- PreferenceFragmentCompat のための設定 -->
        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
        <item name="preferenceCategory_marginBottom">0dp</item>
    </style>
</resources>

これで実行してみる。左側が PreferenceFragmentCompat で、右側が android.preference.PreferenceFragment

f:id:Ginkyo:20180308211021p:plain

微妙にずれている。0dp は小さすぎたようなので、1dp に変えてみる。

<item name="preferenceCategory_marginBottom">1dp</item>

f:id:Ginkyo:20180308211321p:plain

今度はぴったりになった。マージンの色が違うのが気になるけど、まあいいか…。