質問

I need to create a custom ListPreference dialog so that I can add some header text (a TextView) above the List (ListView). I've created MyListPreference class that extends ListPreference and overrides onCreateDialogView():

@Override
protected View onCreateDialogView() {
    LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = (View) inflater.inflate(R.layout.dialog_preference_list, null);
    return v;
}

My XML layout dialog_preference_list.xml contains:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <ListView
        android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:drawSelectorOnTop="false"
        android:scrollbarAlwaysDrawVerticalTrack="true" />

</LinearLayout>

Problem: The TextView is displayed below the ListView instead of above. I need the TextView to be above. I've tried both with LinearLayout and RelativeLayout (using "below" or "above" attributes) with no success: I can't find a way to put the TextView above the ListView... The layout is pretty simple and I cannot see why the list stays above... Also, note that the problem occurs on both a real device (Nexus 4, Android 4.2.2) and the emulator. However, when looking at the layout rendered in Eclipse's graphical layout, the layout is correct! See both attached pictures.

Any idea on how to solve this?

Layout rendered on the device (incorrect):

Layout rendered on the device (incorrect)

Layout rendered on Eclipse (correct):

Layout rendered on Eclipse (correct)

Edit with solution 10.07.2013

As suggested by the accepted answer, the problem comes from the use of builder.setSingleChoiceItems() in ListPreference's onPrepareDialogBuilder(). I've fixed it by extending ListPreference and overriding onCreateDialogView() to build the Dialog without the builder so that I can create a custom View showing the header text above the list items.

GPListPreference.java:

public class GPListPreference extends ListPreference {
    ...

    @Override
    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
        builder.setNegativeButton(null, null);
        builder.setPositiveButton(null, null);
    }

    private int getValueIndex() {
        return findIndexOfValue(getValue());
    }

    @Override
    protected View onCreateDialogView() {
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ListView lv = (ListView) inflater.inflate(R.layout.dialog_preference_list, null);

        TextView header = (TextView) inflater.inflate(R.layout.dialog_preference_list_header, null);
        header.setText(getDialogMessage()); // you should set the header text as android:dialogMessage in the preference XML
        lv.addHeaderView(header);

        ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(getContext(), R.layout.dialog_preference_list_singlechoice, getEntries());
        lv.setAdapter(adapter);

        lv.setClickable(true);
        lv.setEnabled(true);
        lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        lv.setItemChecked(getValueIndex() + 1, true);
        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                setValueIndex(position - 1);
                getDialog().dismiss();
            }
        });

        return lv;
    }
}

dialog_preference_list.xml:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/list"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:drawSelectorOnTop="false"
    android:scrollbarAlwaysDrawVerticalTrack="true" />

dialog_preference_list_singlechoice.xml

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:checkMark="?android:attr/listChoiceIndicatorSingle"
    android:ellipsize="marquee"
    android:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:paddingBottom="2dip"
    android:paddingLeft="10dip"
    android:paddingRight="10dip"
    android:paddingTop="2dip"
    android:textAppearance="?android:attr/textAppearanceMedium" />

dialog_preference_list_header.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dip"
    android:textAppearance="?android:attr/textAppearanceSmall">

</TextView>
役に立ちましたか?

解決

I think the problem is with the way ListPreference works. ListPreference uses Builder.setSingleChoiceItems() to create the rows with the RadioButtons, and it has preference over the custom layout you are trying to add (in your case a TextView and a ListView inside a LinearLayout. The solution is extending DialogPreference instead. Here is a link to a GitHub where I created a custom DialogPreference that does what you need. I haven't coded the RadioButton logic.

他のヒント

I guess it's a theming issue. Try changing the theme of your dialog inside the constructor make it something like setStyle(STYLE_NO_TITLE, R.style.AppTheme). Your base app theme with no_title style.

If this is not the issue than it might be related with the ListPreference class itself. It might be overriding your layout for consistency in theming the preference views. However, I have not used ListPreference before, so its just a guess.

Can you reproduce the same result by playing with the themes in XML graphical layout preview?

Another option you can try is to add the TextView as a header to the ListView like this:

TextView textView = new TextView(getActivity());
ListView listView = new ListView(getActivity());
listView.addHeaderView(textView);

The addHeaderView takes a View so you theoretically have anything you want to be the header, but I have only used a TextView.

The link above is broken. On this solution the idea is overriding the ListPreference, and inflating your own listview, with the data defined on the ListPreference.

@Override
protected View onCreateDialogView() {

    LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ListView lv = new ListView(getContext());

    // Inflate the view into the header only if a message was set
    if (getDialogMessage() != null && ! getDialogMessage().equals("") ) {

        TextView header = (TextView) inflater.inflate(R.layout.dialog_preference_list_header, null);
        header.setText(getDialogMessage());
        lv.addHeaderView(header, null, false);

    }

    // Create a new adapter and a list view and feed it with the ListPreference entries
    ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(getContext(),
            R.layout.custom_dialog_single_choice_list_adapter, getEntries());

    lv.setAdapter(adapter);
    lv.setClickable(true);
    lv.setEnabled(true);
    lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    lv.setItemChecked(getValueIndex() + 1, true);
    lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            setValueIndex(position - 1);
            getDialog().dismiss();
        }
    });
    return lv;
}

Another important thing is to call onPrepareDialogBuilder and not calling super in it. This will avoid that the listview appears twice.

@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
    // Not calling super, to avoid having 2 listviews

    // Set the positive button as null
    builder.setPositiveButton(null, null);
}

private int getValueIndex() {
    return findIndexOfValue(getValue());
}

Where dialog_preference_list_header is in my case only a TestView, but it could be a more complex view, and custom_dialog_single_choice_list_adapter could be something like this:

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:checkMark="?android:attr/listChoiceIndicatorSingle"
    android:ellipsize="marquee"
    android:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:paddingBottom="2dip"
    android:paddingLeft="10dip"
    android:paddingRight="10dip"
    android:paddingTop="2dip"
    android:textAppearance="?android:attr/textAppearanceMedium" />
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top