custom row in a listPreference?

22,874

Solution 1

OK I got this to work, mostly. I had to use a custom defined class that extends ListPreference. Then inside of that I had to create a custom adapter class just like you would for a ListView and set it to the builder using builder.setAdapter(). I also had to define listeners for both the RadioButtons and the ListView rows that handled unchecking of the RadioButtons and such. The only issues I still have are, my custom ListPreference has both an OK and a Cancel button where a ListPreference only has the cancel button. I don't know how to remove the OK button. Also, I can't get the rows to highlight when I click on them like they do in a regular ListPreference.

The java code for the custom ListPreference class. Be sure to mind things like your package name, the preference name (key), your entries and values for the ListPreference, and the names of your xml items.

package your.package.here;

import java.util.ArrayList;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.preference.ListPreference;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.app.Dialog;
import android.app.AlertDialog.Builder;

public class CustomListPreference extends ListPreference
{   
    CustomListPreferenceAdapter customListPreferenceAdapter = null;
    Context mContext;
    private LayoutInflater mInflater;
    CharSequence[] entries;
    CharSequence[] entryValues;
    ArrayList<RadioButton> rButtonList;
    SharedPreferences prefs;
    SharedPreferences.Editor editor;

    public CustomListPreference(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mContext = context;
        mInflater = LayoutInflater.from(context);
        rButtonList = new ArrayList<RadioButton>();
        prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        editor = prefs.edit();
    }

    @Override
    protected void onPrepareDialogBuilder(Builder builder)
    {
        entries = getEntries();
        entryValues = getEntryValues();

        if (entries == null || entryValues == null || entries.length != entryValues.length )
        {
            throw new IllegalStateException(
                    "ListPreference requires an entries array and an entryValues array which are both the same length");
        }

        customListPreferenceAdapter = new CustomListPreferenceAdapter(mContext);

        builder.setAdapter(customListPreferenceAdapter, new DialogInterface.OnClickListener()
        {
            public void onClick(DialogInterface dialog, int which)
            {

            }
        });
    }

    private class CustomListPreferenceAdapter extends BaseAdapter
    {        
        public CustomListPreferenceAdapter(Context context)
        {

        }

        public int getCount()
        {
            return entries.length;
        }

        public Object getItem(int position)
        {
            return position;
        }

        public long getItemId(int position)
        {
            return position;
        }

        public View getView(final int position, View convertView, ViewGroup parent)
        {  
            View row = convertView;
            CustomHolder holder = null;

            if(row == null)
            {                                                                   
                row = mInflater.inflate(R.layout.custom_list_preference_row, parent, false);
                holder = new CustomHolder(row, position);
                row.setTag(holder);

                // do whatever you need here, for me I wanted the last item to be greyed out and unclickable
                if(position != 3)
                {
                    row.setClickable(true);
                    row.setOnClickListener(new View.OnClickListener()
                    {
                        public void onClick(View v)
                        {
                            for(RadioButton rb : rButtonList)
                            {
                                if(rb.getId() != position)
                                    rb.setChecked(false);
                            }

                            int index = position;
                            int value = Integer.valueOf((String) entryValues[index]);
                            editor.putInt("yourPref", value);

                            Dialog mDialog = getDialog();
                            mDialog.dismiss();
                        }
                    });
                }
            }

            return row;
        }

        class CustomHolder
        {
            private TextView text = null;
            private RadioButton rButton = null;

            CustomHolder(View row, int position)
            {    
                text = (TextView)row.findViewById(R.id.custom_list_view_row_text_view);
                text.setText(entries[position]);
                rButton = (RadioButton)row.findViewById(R.id.custom_list_view_row_radio_button);
                rButton.setId(position);

                // again do whatever you need to, for me I wanted this item to be greyed out and unclickable
                if(position == 3)
                {
                    text.setTextColor(Color.LTGRAY);
                    rButton.setClickable(false);
                }

                // also need to do something to check your preference and set the right button as checked

                rButtonList.add(rButton);
                rButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
                {
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
                    {
                        if(isChecked)
                        {
                            for(RadioButton rb : rButtonList)
                            {
                                if(rb != buttonView)
                                    rb.setChecked(false);
                            }

                            int index = buttonView.getId();
                            int value = Integer.valueOf((String) entryValues[index]);
                            editor.putInt("yourPref", value);

                            Dialog mDialog = getDialog();
                            mDialog.dismiss();
                        }
                    }
                });
            }
        }
    }
}

The xml for my PreferenceActivity. This is not my full xml, took out all my other preference items for simplicity. Again, be sure to mind the package name, the custom ListPreference class must be referenced by the package name. Also mind the names of the preference and the array names that hold the entries and values.

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">

        <PreferenceCategory
                android:title="Your Title">

                <your.package.here.CustomListPreference
                    android:key="yourPref"
                    android:title="Your Title"
                    android:dialogTitle="Your Title"
                    android:summary="Your Summary"
                    android:defaultValue="1"
                    android:entries="@array/yourArray"
                    android:entryValues="@array/yourValues"/>

        </PreferenceCategory>
</PreferenceScreen>

My xml for the dialog's list view row. In the getView method be sure to use the name of this xml file in the line that inflates this.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingBottom="8dip"
    android:paddingTop="8dip"
    android:paddingLeft="10dip"
    android:paddingRight="10dip">

    <TableLayout
        android:id="@+id/custom_list_view_row_table_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:stretchColumns="0">

        <TableRow
            android:id="@+id/custom_list_view_row_table_row"
            android:gravity="center_vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/custom_list_view_row_text_view"
                android:textSize="22sp"
                android:textColor="#000000"  
                android:gravity="center_vertical"
                android:layout_width="160dip" 
                android:layout_height="40dip" />

            <RadioButton
                android:checked="false"
                android:id="@+id/custom_list_view_row_radio_button"/>
        </TableRow>
    </TableLayout>

</LinearLayout>

Finally, under res/values here is my array.xml that contains the entry names and values for the ListPreference. Again, shortened mine for simplicity.

<?xml version="1.0" encoding="utf-8"?>
<resources> 
    <string-array name="yourArray">
        <item>Item 1</item>
        <item>Item 2</item>
        <item>Item 3</item>
        <item>Item 4</item>
    </string-array>

    <string-array name="yourValues">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </string-array>
</resources>

Solution 2

This worked well for me. I used an Adapter approach that injects a wrapped adapter into the view.

Here is the base wrapped adapter class:

import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.WrapperListAdapter;

class ListPrefWrapperAdapter implements WrapperListAdapter {
    private ListAdapter mOrigAdapter;

    public ListPrefWrapperAdapter(ListAdapter origAdapter) {
        mOrigAdapter = origAdapter;
    }

    @Override
    public ListAdapter getWrappedAdapter() {
        return mOrigAdapter;
    }

    @Override
    public boolean areAllItemsEnabled() {
        return getWrappedAdapter().areAllItemsEnabled();
    }

    @Override
    public boolean isEnabled(int position) {
        return getWrappedAdapter().isEnabled(position);
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        getWrappedAdapter().registerDataSetObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        getWrappedAdapter().unregisterDataSetObserver(observer);
    }

    @Override
    public int getCount() {
        return getWrappedAdapter().getCount();
    }

    @Override
    public Object getItem(int position) {
        return getWrappedAdapter().getItem(position);
    }

    @Override
    public long getItemId(int position) {
        return getWrappedAdapter().getItemId(position);
    }

    @Override
    public boolean hasStableIds() {
        return getWrappedAdapter().hasStableIds();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return getWrappedAdapter().getView(position, convertView, parent);
    }

    @Override
    public int getItemViewType(int position) {
        return getWrappedAdapter().getItemViewType(position);
    }

    @Override
    public int getViewTypeCount() {
        return getWrappedAdapter().getViewTypeCount();
    }

    @Override
    public boolean isEmpty() {
        return getWrappedAdapter().isEmpty();
    }
}

Here is the CustomListPreference base class that uses the ListPrefWrapperAdapter:

import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.ListAdapter;
import android.widget.ListView;

public class CustomListPreference extends ListPreference {
    public CustomListPreference(Context context) {
        super(context);
    }

    public CustomListPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void showDialog(Bundle state) {
        super.showDialog(state);
        AlertDialog dialog = (AlertDialog) getDialog();
        ListView listView = dialog.getListView();
        ListAdapter adapter = listView.getAdapter();
        final ListPrefWrapperAdapter fontTypeAdapter = createWrapperAdapter(adapter);

        // Adjust the selection because resetting the adapter loses the selection.
        int selectedPosition = findIndexOfValue(getValue());
        listView.setAdapter(fontTypeAdapter);
        if (selectedPosition != -1) {
            listView.setItemChecked(selectedPosition, true);
            listView.setSelection(selectedPosition);
        }
    }

    protected ListPrefWrapperAdapter createWrapperAdapter(ListAdapter origAdapter) {
        return new ListPrefWrapperAdapter(origAdapter);
    }

}

Finally, here are the derived classes that do the disabling and enabling of specific rows:

import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.ListAdapter;

public class FontTypePreference extends CustomListPreference {

    public FontTypePreference(Context context) {
        super(context);
    }

    public FontTypePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected ListPrefWrapperAdapter createWrapperAdapter(ListAdapter origAdapter) {
        return new Adapter(origAdapter);
    }

    private class Adapter extends ListPrefWrapperAdapter {
        private static final float TEXT_SIZE = 25.0f;
        private static final int STARTING_UPGRADE_REQUIRED_INDEX = 8;

        public Adapter(ListAdapter origAdapter) {
            super(origAdapter);
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public boolean isEnabled(int position) {
            return position < STARTING_UPGRADE_REQUIRED_INDEX;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            CheckedTextView textView = (CheckedTextView) getWrappedAdapter()
                    .getView(position, convertView, parent);
            textView.setTextColor(position < STARTING_UPGRADE_REQUIRED_INDEX ?
                    Color.BLACK : Color.RED);
            return textView;
        }


    }

}

I have only tested this code on SDK version 15 and above.

Solution 3

Probably have to add editor.commit(); after each editor.putInt(...)

Solution 4

Thanks Bob for that answer, and Vamsi for trying to correct the duplicate entries bug, but Vamsi's fix didn't work for me. I had to keep an array of views and return it on the position if it had already been created before. So here is my full CustomListPreferenceAdapter class. It also contains the fix to check the selected preference value.

private class CustomListPreferenceAdapter extends BaseAdapter
{
    View[] Views;

    public CustomListPreferenceAdapter(Context context)
    {
        Views = new View[entries.length];
    }

    public int getCount()
    {
        return entries.length;
    }

    public Object getItem(int position)
    {
        return null;
    }

    public long getItemId(int position)
    {
        return position;
    }

    public View getView(final int position, View convertView, ViewGroup parent)
    {  
        View row = Views[position];
        CustomHolder holder = null;

        if(row == null)
        {                                                             
            row = mInflater.inflate(R.layout.listrow, parent, false);
            holder = new CustomHolder(row, position);
            row.setTag(holder);
            Views[position] = row;
        }

        return row;
    }

    class CustomHolder
    {
        private TextView text = null;
        private RadioButton rButton = null;

        CustomHolder(View row, int position)
        {    
            text = (TextView)row.findViewById(R.id.custom_list_view_row_text_view);
            text.setText(entries[position]);

            rButton = (RadioButton)row.findViewById(R.id.custom_list_view_row_radio_button);
            rButton.setId(position);

            if(getPersistedString("").compareTo((String)entryValues[position])==0)
                rButton.setChecked(true);

            rButtonList.add(rButton);
        }
    }
}

Solution 5

function getcount() returns is wrong.

public int getCount()
    {
        return entries.length;
    }

    public Object getItem(int position)
    {
        return null;
    }

    public long getItemId(int position)
    {
        return position;
    }
Share:
22,874

Related videos on Youtube

Bob
Author by

Bob

Updated on July 09, 2022

Comments

  • Bob
    Bob almost 2 years

    I am trying to create a ListPreference but somehow disable one of the items. Sort of like gray it out or something and not have the ability to choose it. It will be an upcoming feature and I want it to be in the list just not selectable.

    I have created a custom ListPreference class and in that class a custom adapter, hoping to use the adapter to create what I want.

    The code works, and it sets the adapter, but none of the adapter functions get called. I set breakpoints on the methods, such as getCount() but they never get called.

    Here's my code. Custom ListPreference taken from http://blog.350nice.com/wp/archives/240

    import android.content.Context;
    import android.content.DialogInterface;
    import android.graphics.Color;
    import android.preference.ListPreference;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.app.AlertDialog.Builder;
    
    public class CustomListPreference extends ListPreference {
    
        private boolean[] mClickedDialogEntryIndices;
        CustomListPreferenceAdapter customListPreferenceAdapter = null;
        Context mContext;
    
        public CustomListPreference(Context context, AttributeSet attrs) {
            super(context, attrs);
            mContext = context;
            mClickedDialogEntryIndices = new boolean[getEntries().length];
        }
    
        @Override
        protected void onPrepareDialogBuilder(Builder builder) {
            CharSequence[] entries = getEntries();
            CharSequence[] entryValues = getEntryValues();
            if (entries == null || entryValues == null
                    || entries.length != entryValues.length) {
                throw new IllegalStateException(
                        "ListPreference requires an entries array "
                        +"and an entryValues array which are both the same length");
            }
            builder.setMultiChoiceItems(entries, mClickedDialogEntryIndices,
                    new DialogInterface.OnMultiChoiceClickListener() {
    
                        public void onClick(DialogInterface dialog, int which,
                                boolean val) {
                            mClickedDialogEntryIndices[which] = val;
                        }
                    });
            // setting my custom list adapter
            customListPreferenceAdapter = new CustomListPreferenceAdapter(mContext);
            builder.setAdapter(customListPreferenceAdapter, null);
        }
    
        private class CustomListPreferenceAdapter extends BaseAdapter {
    
            public CustomListPreferenceAdapter(Context context) {}
    
            public int getCount() {
                return 1;
            }
    
            public Object getItem(int position) {
                return position;
            }
    
            public long getItemId(int position) {
                return position;
            }
    
            public View getView(int position, View convertView, ViewGroup parent) {
                convertView.setBackgroundColor(Color.BLUE);
                return convertView;
            }
        }
    }
    
    • CaseyB
      CaseyB over 13 years
      How are you using this CustomListPreference? Are you inflating the ui for your PreferenceActivity from xml? Building it in code?
    • Bob
      Bob over 13 years
      I am defining the preference for my PreferenceActivity via xml, and in that xml I have an item that is this custom listPreference, using the full package name to reference this class.
    • Bob
      Bob over 13 years
      Update: I got the adapter to work by using builder.setSingleChoiceItems() and passing in my adapter as the first argument. This method is overloaded a few times so if you are reading this just look it up in documentation. Working on getting it all tied together now.
  • Igoussam
    Igoussam over 13 years
    To remove the OK button put "builder.setPositiveButton(null, null);" just after "protected void onPrepareDialogBuilder(Builder builder) {"
  • Vamsi
    Vamsi about 12 years
    This is working perfectly until the number of items(in your case its 4) exceeds the screen size; can you kindly look that once? In the case when elements exceed screen size the older elements repeat instead of showing the next elements
  • Vamsi
    Vamsi about 12 years
    repeating entries when the number of entries is more than the screen accommodated entries has been solved, will post the code in some time
  • CooL i3oY
    CooL i3oY almost 12 years
    selected radiobutton not working.and its unchecked! how to solve this?!
  • CooL i3oY
    CooL i3oY almost 12 years
    this is simulated Project that has problem.problem is that selected radiobutton not work s1.picofile.com/file/7491144301/…
  • jfortunato
    jfortunato over 11 years
    this will disable the entire ListPreference, and the OP only wants to disable some items INSIDE the ListPreference
  • Dory
    Dory almost 11 years
    Hello @Vamsi. I am trying to make custom list preference everything works fine, until when the number of items increases screen size,here list view in dialog repeats the older items instead of new item. As you posted in one of comment you have solved this problem, please guide me how did you solve it. Thanks.
  • Vamsi
    Vamsi almost 11 years
    @NidhiGondhia Please check the newly added answer. That worked in my case.
  • impossible
    impossible almost 8 years
    @Vamsi Hey, I am kind of stuck. I have the same problem as it is here. But the implementation is bit different. Here is my question. Can you help me with this? stackoverflow.com/questions/38331665/…
  • impossible
    impossible almost 8 years
    Hey, I am kind of stuck. I have the same problem as it is here. But the implementation is bit different. Here is my question. Can you help me with this? stackoverflow.com/questions/38331665/…
  • impossible
    impossible almost 8 years
    Hey, I did comment at top but in case you don't quite notice it. I am requesting you help me with my problem stackoverflow.com/questions/38331665/…
  • M.Paunov
    M.Paunov almost 7 years
    Also we should call callChangeListener(newValue); after we edit the preference to make sure any OnPreferenceChangeListener gets activated from the change.