android - fila personalizada en una listaPreference?
listadapter listpreference (9)
ListPreference
crear una ListPreference
pero de alguna manera desactivar uno de los elementos. Algo así como gris o algo así y no tener la capacidad de elegirlo. Será una función próxima y quiero que esté en la lista simplemente no seleccionable.
ListPreference
clase de ListPreference
personalizada y en esa clase un adaptador personalizado, con la esperanza de usar el adaptador para crear lo que quiero.
El código funciona y configura el adaptador, pero no se llama a ninguna de las funciones del adaptador. Establecí puntos de interrupción en los métodos, como getCount()
pero nunca se llaman.
Aquí está mi código. Custom ListPreference tomada de 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;
}
}
}
Creo que puede lograr exactamente lo que desea estableciendo el marcador habilitado de ListPreference en falso:
ListPreference lp = (ListPreference) findPreference("YOUR_KEY");
lp.setEnabled(false);
Esto pone de relieve la descripción y la hace no seleccionable.
Esto funcionó bien para mí. Usé un enfoque de Adaptador que inyecta un adaptador envuelto en la vista.
Aquí está la clase de adaptador envuelto por la base:
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();
}
}
Aquí está la clase base CustomListPreference que usa el 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);
}
}
Finalmente, aquí están las clases derivadas que desactivan y habilitan las filas específicas:
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;
}
}
}
Solo he probado este código en SDK versión 15 y superior.
Esto funcionó para mí, pero no funcionó bien si la lista no cabe en la pantalla (y requiere desplazamiento). Me llevó mucho tiempo encontrar la solución (pero finalmente lo hice).
Primero, el problema: como se describe aquí: getView llamado con la posición incorrecta cuando se desplaza rápidamente obtendrá un comportamiento impredecible cuando se utiliza un oyente onclick en:
public View getView(final int position, View convertView, ViewGroup parent)
En mi caso, el evento onClick se almacenaría en la memoria y se ejecutaría cuando el usuario intentara desplazarse (ligeramente).
Y ahora la solución: poner el oyente onClick en la clase principal (al menos esto funcionó para mí):
public class CustomListPreference extends ListPreference {
// Other code (see above)
@Override
protected void onPrepareDialogBuilder(Builder builder)
{
builder.setPositiveButton(null, null);
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 position)
{
// Code here, using position to indicate the row that was clicked...
dialog.dismiss();
}
});
}
Pasa demasiado tiempo con esto, así que espero que ayude a alguien :)
Con todo, ¡todavía estoy realmente feliz con este ejemplo de código! (úselo como selector de color).
PD: si te gusta esta publicación, vote útil. ¡Gracias!
Gracias Bob por esa respuesta, y Vamsi por tratar de corregir el error de las entradas duplicadas, pero la solución de Vamsi no funcionó para mí. Tenía que mantener una variedad de vistas y devolverlas en la posición si ya se habían creado antes. Así que aquí está mi clase CustomListPreferenceAdapter completa. También contiene la solución para verificar el valor de preferencia seleccionado.
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);
}
}
}
OK, tengo esto para trabajar, principalmente. Tuve que usar una clase definida personalizada que amplía ListPreference
. Luego, dentro de eso tuve que crear una clase de adaptador personalizado como lo haría para un ListView
y configurarlo para el constructor usando builder.setAdapter()
. También tuve que definir oyentes tanto para RadioButtons
como para las filas de ListView
que manejaban la desactivación de RadioButtons
y RadioButtons
. Los únicos problemas que aún tengo son que mi ListPreference
personalizado tiene un botón Aceptar y otro de Cancelar, donde una ListPreference
solo tiene el botón Cancelar. No sé cómo eliminar el botón Aceptar. Además, no puedo resaltar las filas cuando hago clic en ellas como lo hacen en una ListPreference
normal.
El código de java para la clase personalizada de ListPreference
. Asegúrese de tener en cuenta cosas como el nombre de su paquete, el nombre de preferencia (clave), sus entradas y valores para ListPreference
, y los nombres de sus elementos xml.
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();
}
}
});
}
}
}
}
El xml para mi PreferenceActivity
. Este no es mi xml completo, eliminé todos mis otros elementos de preferencia por simplicidad. Nuevamente, asegúrese de tener en cuenta el nombre del paquete, la clase personalizada de ListPreference
debe ser referenciada por el nombre del paquete. También tenga en cuenta los nombres de las preferencias y los nombres de los arreglos que contienen las entradas y los valores.
<?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>
Mi xml para la fila de vista de lista del diálogo. En el método getView, asegúrese de usar el nombre de este archivo xml en la línea que lo infla.
<?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>
Finalmente, bajo res / values aquí está mi matriz.xml que contiene los nombres y valores de entrada para la ListPreference
. Nuevamente, abreviada mina por simplicidad.
<?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>
Probablemente tenga que agregar editor.commit()
; después de cada editor.putInt(...)
Puedes hacerlo más fácilmente.
Pasos:
Extender ListPreference
public class CustomListPreference extends ListPreference { Context mContext; public CustomListPreference(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } }
Anular onPrepareDialogBuilder y reemplazar mBuilder en DialogPreference con ProxyBuilder:
@Override protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder builder){ super.onPrepareDialogBuilder(builder); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.FROYO) { return; } // Inject Builder Proxy for intercepting of getView. try { Field privateBuilderField = DialogPreference.class.getDeclaredField("mBuilder"); privateBuilderField.setAccessible(true); privateBuilderField.set(this, new ProxyBuilder(mContext, (android.app.AlertDialog.Builder)privateBuilderField.get(this))); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
Manejar getView en ProxyBuilder-> AlertDialog-> onShow-> getListView-> Adapter
private class ProxyBuilder extends android.app.AlertDialog.Builder{ android.app.AlertDialog.Builder mBuilder; private ProxyBuilder(Context context, AlertDialog.Builder builder) { super(context); mBuilder = builder; } @TargetApi(Build.VERSION_CODES.FROYO) @Override public AlertDialog create() { AlertDialog alertDialog = mBuilder.create(); alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { ListView listView = ((AlertDialog)dialog).getListView(); final ListAdapter originalAdapter = listView.getAdapter(); listView.setAdapter(new ListAdapter(){ @Override public int getCount() { return originalAdapter.getCount(); } @Override public Object getItem(int id) { return originalAdapter.getItem(id); } @Override public long getItemId(int id) { return originalAdapter.getItemId(id); } @Override public int getItemViewType(int id) { return originalAdapter.getItemViewType(id); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = originalAdapter.getView(position, convertView, parent); TextView textView = (TextView)view; textView.setTextColor(Color.RED); return view; } @Override public int getViewTypeCount() { return originalAdapter.getViewTypeCount(); } @Override public boolean hasStableIds() { return originalAdapter.hasStableIds(); } @Override public boolean isEmpty() { return originalAdapter.isEmpty(); } @Override public void registerDataSetObserver(DataSetObserver observer) { originalAdapter.registerDataSetObserver(observer); } @Override public void unregisterDataSetObserver(DataSetObserver observer) { originalAdapter.unregisterDataSetObserver(observer); } @Override public boolean areAllItemsEnabled() { return originalAdapter.areAllItemsEnabled(); } @Override public boolean isEnabled(int position) { return originalAdapter.isEnabled(position); } }); } }); return alertDialog; } }
la función getcount()
regresa es incorrecta.
public int getCount()
{
return entries.length;
}
public Object getItem(int position)
{
return null;
}
public long getItemId(int position)
{
return position;
}
modificó el código como abajo -
if(row == null) {
row = mInflater.inflate(R.layout.custom_list_preference_row, parent, false);
holder = new CustomHolder(row, position);
} else {
holder = row.getTag()
}
// update the holder with new Text/Drawables etc.,
row.setTag(holder);
return row;
PD: NidhiGondhia solicitó un código modificado, ya que en los comentarios esto no se puede ajustar, actualizando aquí el código modificado.