19

I have a couple of custom DialogPreference implementations floating around, such as this one:

package apt.tutorial;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {
    private int lastHour=0;
    private int lastMinute=0;
    private TimePicker picker=null;

    public static int getHour(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[0]));
    }

    public static int getMinute(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[1]));
    }

    public TimePreference(Context ctxt) {
        this(ctxt, null);
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        this(ctxt, attrs, 0);
    }

    public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
        super(ctxt, attrs, defStyle);

        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    protected View onCreateDialogView() {
        picker=new TimePicker(getContext());

        return(picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);

            if (callChangeListener(time)) {
                persistString(time);
            }
        }
    }

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

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        String time=null;

        if (restoreValue) {
            if (defaultValue==null) {
                time=getPersistedString("00:00");
            }
            else {
                time=getPersistedString(defaultValue.toString());
            }
        }
        else {
            time=defaultValue.toString();
        }

        lastHour=getHour(time);
        lastMinute=getMinute(time);
    }
}

They work just fine. However, in an application with android:targetSdkVersion="11" defined, on a XOOM, they show up missing the indent when in the PreferenceActivity:

PreferenceActivity with messed-up custom DialogPreference

Also, the font size appears a smidge bigger, at least for the title.

There's nothing in DialogPreference where I am really overriding any formatting behavior for that stuff, AFAIK. The preference XML is unremarkable, other than referring to the above class:

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <ListPreference
        android:key="sort_order"
        android:title="Sort Order"
        android:summary="Choose the order the list uses"
        android:entries="@array/sort_names"
        android:entryValues="@array/sort_clauses"
        android:dialogTitle="Choose a sort order" />
    <CheckBoxPreference
        android:key="alarm"
        android:title="Sound a Lunch Alarm"
        android:summary="Check if you want to know when it is time for lunch" />
    <apt.tutorial.TimePreference
        android:key="alarm_time"
        android:title="Lunch Alarm Time"
        android:defaultValue="12:00"
        android:summary="Set your desired time for the lunch alarm"
        android:dependency="alarm" />
    <CheckBoxPreference
        android:key="use_notification"
        android:title="Use a Notification"
        android:defaultValue="true"
        android:summary="Check if you want a status bar icon at lunchtime, or uncheck for a full-screen notice"
        android:dependency="alarm" />
</PreferenceScreen>

Anyone know where I'm going wrong?

Thanks!


UPDATE

Here is a link to a project that contains this custom preference and a simple preference XML file that demonstrates the problem. Even with just two Java classes, the preference XML, and an arrays.xml file, I get this phenomenon. Here is a compiled APK from this project.

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491

5 Answers5

14

(cross-posting from the associated android-developers thread)

OK, I figured it out.

There are three possible constructors on a Preference:

MyPreference(Context ctxt)
MyPreference(Context ctxt, AttributeSet attrs)
MyPreference(Context ctxt, AttributeSet attrs, int defStyle)

Somewhere along the line, I picked up the pattern of having the one-parameter constructor chain to the two-parameter constructor (passing null for the 2nd parameter), and having the two-parameter constructor chain to the three-parameter constructor (passing 0 for the 3rd parameter).

And that's not the right answer.

I am hoping that the right answer is to only implement the second constructor, because the correct default style is internal to Android (com.android.internal.R.attr.dialogPreferenceStyle). The second constructor is the one used with inflating preference XML.

Thanks to all for the help!

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Is the solution to simply to put "com.android.internal.R.attr.dialogPreferenceStyle" instead of 0 as the third attribute that's passed to the third constructor? I had the same problem as you a while back and eventually gave up on it. – Tenfour04 Jul 31 '11 at 17:40
  • @TenFour04: You don't have access to `com.android.internal.R.attr.dialogPreferenceStyle`. – CommonsWare Jul 31 '11 at 20:31
  • Well that's no fun. I don't understand why certain resources are restricted, and others aren't. For instance, you can use list_selector_background, but not list_selector_holo_dark. Thanks. – Tenfour04 Jul 31 '11 at 21:36
  • 5
    @Maxim: Yes, implementing the two-parameter constructor to chain to the superclass solved the problem. – CommonsWare Dec 01 '11 at 22:45
  • 2
    It works if we extend preference from one of standard preferences like EditPreference. What if we what to create a custom layout for our preference and extend it from base Preference class? I can't get it intended. Wondering if you tried case like that? – Maxim Dec 16 '11 at 18:06
  • @Maxim: I have only used `DialogPreference` as the base class, sorry. – CommonsWare Dec 16 '11 at 18:07
  • @CommonsWare Doesn't work for me... This is an old post, but do you think you still have your preference layout file, it doesn't seem to be in the download. Also the solution given by Ruslan works for making the preference normal, but my widget is on the right of the summary not below it and I can't change that. – Nicolas Apr 14 '17 at 01:05
  • @NicolasMaltais: I haven't look at this in years. The only custom `Preference` I still (sorta) have is [this one](https://github.com/commonsguy/cwac-colormixer/blob/master/colormixer/src/com/commonsware/cwac/colormixer/ColorPreference.java) from a discontinued library. – CommonsWare Apr 14 '17 at 11:46
6

You can dance with void Preference.setWidgetLayoutResource(int widgetLayoutResId) method, although I prefer to override View Preference.onCreateView(ViewGroup parent) method in my custom Preference class and hack it by adding custom views just below @android:id/summary (use hierarchyviewer utility for details).

The complete method is:

@Override
protected View onCreateView(ViewGroup parent)
{
    View ret = super.onCreateView(parent);

    View summary = ret.findViewById(android.R.id.summary);
    if (summary != null)
    {
        ViewParent summaryParent = summary.getParent();
        if (summaryParent instanceof ViewGroup)
        {
            final LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            ViewGroup summaryParent2 = (ViewGroup) summaryParent;
            layoutInflater.inflate(R.layout.seek_bar_preference, summaryParent2);

            seekBar = (SeekBar) summaryParent2.findViewById(R.id.seekBar);
            seekBar.setMax(maxValue - minValue);
            seekBar.setOnSeekBarChangeListener(this);

            statusText = (TextView) summaryParent2.findViewById(R.id.seekBarPrefValue);

            unitsRightView = (TextView) summaryParent2.findViewById(R.id.seekBarPrefUnitsRight);
            unitsLeftView = (TextView) summaryParent2.findViewById(R.id.seekBarPrefUnitsLeft);
        }
    }

    return ret;
}

Source code of my SeekBarPreference class based on code from http://robobunny.com can be downloaded here image1 image2

Ruslan Yanchyshyn
  • 2,774
  • 1
  • 24
  • 22
1

The solution which helped me:

I have replaced

public TimePreference(Context ctxt, AttributeSet attrs) {
    this(ctxt, attrs, 0);
}

with

public TimePreference(Context ctxt, AttributeSet attrs) {
    this(ctxt, attrs, ctxt.getResources().getSystem().getIdentifier("dialogPreferenceStyle", "attr", "android"));
}

As you can see, I replaced third argument 0 with ctxt.getResources().getSystem().getIdentifier("dialogPreferenceStyle", "attr", "android") in the second constructor of custom preference class.

1

To make the accepted answer more clear. You only need this constructor:

public TimePreference(Context ctxt, AttributeSet attrs) {
    // this(ctxt, attrs, 0); // wrong
    super(ctxt, attrs); 
}
Gunnar Bernstein
  • 6,074
  • 2
  • 45
  • 67
1

I tried your code on the emulator. There is no problem with the code that you have given, and all the lines have the same formatting; but they all look more similar (in format) to the third preference (Lunch Alarm Time) than the others.

It looks like the other three preferences are getting indented more than required. So, maybe you have some global formatting style that is used, but not picked up by the TimePreference preference.

EDIT: OK. So, the above is not (completely) true. There is definitely a problem when I tried with the target sdk set to HoneyComb. But on setting the theme for the PreferenceActivity class as android:theme="@android:style/Theme.Black", there is a consistency in the look of all the preferences as shown below.

enter image description here

This style looks similar to Froyo, but not the HoneyComb; in the latter, the title font is smaller and there is more indentation. Probably, the default theme is not being assigned to Custom Preferences - just a guess :) A workaround would be to assign the default theme to your preference activity explicitly, but I don't know what the default theme in HoneyComb is (and whether it can be set).

Rajath
  • 11,787
  • 7
  • 48
  • 62
  • @Rajath DSouza: I get the same behavior on the 3.0 emulator as I do on the XOOM. What emulator did you try? – CommonsWare Apr 23 '11 at 12:26
  • I tried it with the Android SDK emulator with Target set to `Android 3.0 (API level 11)` and Skin to `WXGA`. – Rajath Apr 23 '11 at 12:31
  • @Rajath DSouza: How completely strange. I have no styles at all in this particular project, so that wouldn't seem like the source of the problem. I'll have to do some more experimenting and try to narrow the problem down a bit. – CommonsWare Apr 23 '11 at 12:36
  • @Rajath DSouza: Just to confirm -- you have `android:targetSdkVersion="11"` in your manifest, not just having the build target set to 11, right? – CommonsWare Apr 25 '11 at 22:27
  • @CommonsWare: No, it looks like I've been linking with Froyo (remnants from a previous project in Eclipse) all this time. When I did set `android:targetSdkVersion="11"`, I'm seeing the same error you have :D ... Sorry to take you off on a wrong trail. – Rajath Apr 26 '11 at 08:11
  • @CommonsWare: Please see update above based on some tinkering with styles. – Rajath Apr 26 '11 at 08:42
  • @Rajath DSouza: Based upon a comment from Dianne Hackborn on the `android-developers` Google Group, this is indeed something tied to the holographic theme that you get by default with `android:targetSdkVersion="11"`. Current indications are that I will need to set the layout for the preference proper (i.e., the stuff that goes right on the `PreferenceScreen`), even though I should be inheriting the right one. I'll be working on this in the next day or two and will update matters here when I have something going. Thanks again for the help! – CommonsWare Apr 26 '11 at 11:07
  • @Rajath DSouza: I figured out the problem -- check out my answer to my question above. Thanks for your assistance with this! – CommonsWare Apr 26 '11 at 15:22