5

I would like to adjust the default margin/padding values for the 'PreferenceScreen' in Android, I know it's not possible to simply set it in the XML code (layout file) but Android definitely gets these values from somewhere and PROBABLY (I hope) I can set some 'style attribute(s)' and achieve it.

The values I want to adjust are:

enter image description here

I found this answer online: Android: How to maximize PreferenceFragment width (or get rid of margin)?

but as per me it really uses a really bad workaround and I would like to try to manipulate some of the official parameters.

Does anyone knows how can I achieve it by adjusting the style(s) or some other fields?

Community
  • 1
  • 1
Rafael Lima
  • 3,079
  • 3
  • 41
  • 105

1 Answers1

4

Unfortunately, I don't think that you are going to find a better answer than you have already discovered in Android: How to maximize PreferenceFragment width (or get rid of margin)?. I will explain why and give you an alternative which you may consider to be worse than the one presented in the answer you reference.

Underneath the preference screen is a layout for each item which we will identify. These are the preference library dependencies that we will use:

implementation 'com.android.support:preference-v7:27.1.1'
implementation 'com.android.support:preference-v14:27.1.1'

The preferenceTheme attribute of the theme defines how the preferences will look.

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <!-- Theme for the preferences -->
    <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>

Going up the parent chain (ctrl-B) from PreferenceThemeOverlay.v14.Material we see

<style name="PreferenceThemeOverlay.v14.Material">
...
<item name="preferenceStyle">@style/Preference.Material</item>
...
</style>    

Preference.Material is defined as:

<style name="Preference.Material">
    <item name="android:layout">@layout/preference_material</item>
</style>    

The layout for the preference is preference_material.xml. Here is the source.

The following section of this layout interests us:

...
<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:paddingTop="16dp"
    android:paddingBottom="16dp">
    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:textAppearance="?attr/textAppearanceListItem"
        android:ellipsize="marquee" />
    <TextView android:id="@+id/summary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:layout_alignStart="@id/title"
        android:textAppearance="?attr/textAppearanceListItemSecondary"
        android:textColor="?attr/textColorSecondary"
        android:maxLines="10"
        android:ellipsize="end" />
</RelativeLayout>
...

As you can see, the top and bottom padding for the RelativeLayout is hard-coded. Since a style attribute is not used, there is no way to override the padding. Because of this hard-coding, you have two choices:

  1. Use the "really bad workaround" outlined in the answer you found which involves Java code to modify the padding, or,

  2. Use your own layout by defining android:layout="@layout/custom_preference" in the XML for your preferences. You can copy the Android layout and make your modifications.

Each method has drawbacks, so pick the one you think you can best deal with.


The following are key components of a small app that demonstrates substitution of the preference layout.

MainActivity.java
This activity rolls in the preference fragment for convenience.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            Fragment preferenceFragment = new PrefsFragment();
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(R.id.prefContainer, preferenceFragment);
            ft.commit();
        }
    }

    public static class PrefsFragment extends PreferenceFragmentCompat {

        @Override
        public void onCreatePreferences(Bundle bundle, String s) {
            addPreferencesFromResource(R.xml.app_preferences);
        }
    }
}

app_preference.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.Preference
        android:key="preference"
        android:layout="@layout/custom_pref_layout"
        android:summary="Doesn't really do anything."
        android:title="Preference Title" />

    <android.support.v7.preference.EditTextPreference
        android:defaultValue="Default EditTextPreference value"
        android:dialogMessage="EditTextPreference Dialog Message"
        android:inputType="number"
        android:key="editTextPreference"
        android:layout="@layout/custom_pref_layout"
        android:summary="EditTextPreference Summary"
        android:title="EditTextPreference Title" />

    <android.support.v7.preference.SwitchPreferenceCompat
        android:defaultValue="true"
        android:key="switchPreference"
        android:layout="@layout/custom_pref_layout"
        android:summary="SwitchPreference Summary"
        android:title="SwitchPreference Title" />

    <android.support.v7.preference.CheckBoxPreference
        android:defaultValue="true"
        android:key="checkBoxPreference"
        android:layout="@layout/custom_pref_layout"
        android:summary="CheckBoxPreference Summary"
        android:title="CheckBoxPreference Title" />

    <android.support.v7.preference.ListPreference
        android:defaultValue="180"
        android:entries="@array/pref_sync_frequency_titles"
        android:entryValues="@array/pref_sync_frequency_values"
        android:key="list_preference"
        android:layout="@layout/custom_pref_layout"
        android:negativeButtonText="@null"
        android:positiveButtonText="@null"
        android:title="List Preference Title" />

</android.support.v7.preference.PreferenceScreen>

activity_main.xml
Simply defines a home for the preference fragment.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/prefContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.preferencecustomlayout.MainActivity" />

custom_pref_layout.xml
Some modification have been made to accommodate usage of this file mainly updated ?attr/somthing to ?android:attr/something.

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
          http://www.apache.org/licenses/LICENSE-2.0
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<!-- Layout for a Preference in a PreferenceActivity. The
     Preference is able to place a specific widget for its particular
     type in the "widget_frame" layout. -->

<!-- Modified from the original to accommodate usage as a local layout file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:attr/activatedBackgroundIndicator"
    android:clipToPadding="false"
    android:gravity="center_vertical"
    android:minHeight="?attr/listPreferredItemHeightSmall"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart">

    <LinearLayout
        android:id="@+id/icon_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="-4dp"
        android:gravity="start|center_vertical"
        android:minWidth="60dp"
        android:orientation="horizontal"
        android:paddingBottom="4dp"
        android:paddingEnd="12dp"
        android:paddingTop="4dp">

        <com.android.internal.widget.PreferenceImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxHeight="48dp"
            android:maxWidth="48dp" />
    </LinearLayout>

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingBottom="16dp"
        android:paddingTop="16dp">

        <TextView
            android:id="@android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:singleLine="true"
            android:textAppearance="?attr/textAppearanceListItem" />

        <TextView
            android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignStart="@android:id/title"
            android:layout_below="@android:id/title"
            android:ellipsize="end"
            android:maxLines="10"
            android:textAppearance="?attr/textAppearanceListItemSecondary"
            android:textColor="?android:attr/textColorSecondary" />
    </RelativeLayout>
    <!-- Preference should place its actual preference widget here. -->
    <LinearLayout
        android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="end|center_vertical"
        android:orientation="vertical"
        android:paddingStart="16dp" />
</LinearLayout>

Gradle file

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion '27.0.3'

    defaultConfig {
        applicationId "com.example.preferencecustomlayout"
        minSdkVersion 18
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:preference-v7:27.1.1'
    implementation 'com.android.support:preference-v14:27.1.1'
}
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • I tried option 2: copy the file from that link, and them on my `settings.xml` i edited ``... but changing on `preferences_layout` doesn't reflect on preference screen at runtime – Rafael Lima Aug 06 '18 at 02:40
  • @RafaelLima Did you change the value of the padding in `preference_layout`? If you did and don't see the change, post your `settings.xml` here.. – Cheticamp Aug 06 '18 at 11:08
  • sure i did... no changes on it take effect at runtime... how is this layout attribute supposed to work on `settings.xml` ? i mean, i could put any non linear layout there, how would android guess where to put each component of the generated preferencescreen? is is based on the ids? anyway i didn't changed the ids or structure. i just changed the parameters for margin and padding and zero changes were applied – Rafael Lima Aug 06 '18 at 12:34
  • @RafaelLima It is based upon the ids, so it can't be just any old layout. My analysis was based upon the switch preference. If you are using something different, then this approach may need to change. Post your `settings.xml` so we can take a look at what you are doing. You can apply your updated layout to your switch preferences just to see how it works. – Cheticamp Aug 06 '18 at 12:40
  • 1
    it worked, maybe the first time i tried didn't work because i set the `android:layout="@layout/custom_preference"`only at root of `settings.xml` and now with your example i've seen i need to set it for every item – Rafael Lima Aug 08 '18 at 02:16
  • This is perfect. Only small hitch is that in custom_pref_layout.xml you should use `android:id="@android:id/icon"`instead of `@+id/icon`. The latter will add a new ID and the icon can't be set by the Preferences system. – zilp Jun 02 '21 at 07:21