custom row in a listPreference?
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;
}
Related videos on Youtube
Bob
Updated on July 09, 2022Comments
-
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 over 13 yearsHow are you using this CustomListPreference? Are you inflating the ui for your PreferenceActivity from xml? Building it in code?
-
Bob over 13 yearsI 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 over 13 yearsUpdate: 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 over 13 yearsTo remove the OK button put "builder.setPositiveButton(null, null);" just after "protected void onPrepareDialogBuilder(Builder builder) {"
-
Vamsi about 12 yearsThis 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 about 12 yearsrepeating 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 almost 12 yearsselected radiobutton not working.and its unchecked! how to solve this?!
-
CooL i3oY almost 12 yearsthis is simulated Project that has problem.problem is that selected radiobutton not work s1.picofile.com/file/7491144301/…
-
jfortunato over 11 yearsthis will disable the entire ListPreference, and the OP only wants to disable some items INSIDE the ListPreference
-
Dory almost 11 yearsHello @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 almost 11 years@NidhiGondhia Please check the newly added answer. That worked in my case.
-
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 almost 8 yearsHey, 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 almost 8 yearsHey, 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 almost 7 yearsAlso we should call callChangeListener(newValue); after we edit the preference to make sure any OnPreferenceChangeListener gets activated from the change.