Cómo implementar correctamente un adaptador para un ListView

Tengo un ListView en un Fragment y quiero actualizar los datos en el ListView cuando vuelvo de otra Activity . He sobrescrito el método onResume() en el Fragment , modificar los datos en el Adapter y llamar a notifyDataSetChanged() en el Adpater pero de alguna manera el ListView no se está actualizando. Sospecho que hay algo mal con mi Adapter , pero no puedo encontrar el error.

Aquí está el código de mi Adpater :

 class ManualExceptionsListAdapter extends BaseAdapter { private LayoutInflater mInflater; private TextView mManualExceptions; SwitchCompat mSwitch; TextView name; final Context context = getActivity(); final SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); int a; int ifUse = 0; ManualExceptionsListAdapter(LayoutInflater inflater) { mInflater = inflater; } @Override public int getCount() { return (mPermanentManualException.size()+mContactsExceptionNumber.size()); } @Override public Object getItem(int i) { return null; } @Override public long getItemId(int i) { return 0; } @Override public int getItemViewType(int position) { if (position < (mContactsExceptionNumber.size())) { a = 0; if(position == (mContactsExceptionNumber.size()-1)){ ifUse = 1; } return a; } else { a = 1; return a; } } @Override public int getViewTypeCount() { return 2; } @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); } @Override public View getView(int i, View view, ViewGroup viewGroup) { final int pos; if(mContactsExceptionNumber.size()>0) { pos = i - (mContactsExceptionNumber.size()); }else{ pos = 0; } int pos2 = 0; int type = getItemViewType(i); if(ifUse == 1){ if(mContactsExceptionNumber.size()>0) { pos2 = i - (mContactsExceptionNumber.size()); Exceptions.index = pos2; } } View v = view; if (view == null) { switch (type) { case 0: v = mInflater.inflate(R.layout.contacts_exception_row, null); name = (TextView) v.findViewById(R.id.contact_name); name.setText(mContactsExceptionNames.get(i)); break; case 1: v = mInflater.inflate(R.layout.manual_exception_row, null); mManualExceptions = (TextView) v.findViewById(R.id.manual_exception_number); mSwitch = (SwitchCompat) v.findViewById(R.id.manual_exception_switch); mManualExceptions.setText(mPermanentManualException.get(pos2)); mSwitch.setTag(i); try { if (mManualExceptionList.contains(mPermanentManualException.get(pos2))) { mSwitch.setChecked(true); } } catch (Exception e) { e.printStackTrace(); } break; } }else{ switch (type) { case 0: v = mInflater.inflate(R.layout.contacts_exception_row, null); name = (TextView) v.findViewById(R.id.contact_name); name.setText(mContactsExceptionNames.get(i)); break; case 1: v = mInflater.inflate(R.layout.manual_exception_row, null); mManualExceptions = (TextView) v.findViewById(R.id.manual_exception_number); mSwitch = (SwitchCompat) v.findViewById(R.id.manual_exception_switch); mManualExceptions.setText(mPermanentManualException.get(pos2)); mSwitch.setTag(i); try { if (mManualExceptionList.contains(mPermanentManualException.get(pos2))) { mSwitch.setChecked(true); } } catch (Exception e) { e.printStackTrace(); } break; } } try { mSwitch.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { isTouched = true; return false; } }); } catch (NullPointerException e) { e.printStackTrace(); } try { mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if (isTouched) { if (b) { if (!mManualExceptionList.contains((mPermanentManualException.get(pos)))) { mManualExceptionList.add((mPermanentManualException.get(pos))); } mSharedPreferences.edit().putString("ManualExceptions", TextUtils. join(",", mManualExceptionList)).apply(); } else { try { mManualExceptionList.remove((mPermanentManualException.get(pos))); mSharedPreferences.edit().putString("ManualExceptions", TextUtils. join(",", mManualExceptionList)).apply(); } catch (Exception e) { e.printStackTrace(); } } Log.d("RejectCall", "Permanent " + TextUtils.join(",", mPermanentManualException)); Log.d("RejectCall", TextUtils.join(",", mManualExceptionList)); } } }); } catch (NullPointerException e) { e.printStackTrace(); } return v; } } 

Hay múltiples problemas con la implementación del Adapter . Demasiados para que yo le dé consejos sobre cómo solucionarlo. Sólo voy a explicar cómo puede implementar de forma eficiente un Adapter y, a continuación, puede aplicar esto a su Adapter .

Baste decir que usted debe cambiar óptimamente a usar el nuevo RecyclerView que tiene muchas mejoras importantes sobre el viejo ListView . Puede encontrar la documentación de RecyclerView aquí y la guía de Googles sobre cómo usarla aquí .


Si desea mostrar datos en un ListView primero debe crear el diseño para cada uno de los elementos en el ListView . Para este ejemplo usaré este diseño:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="12dp"> <CheckBox android:id="@+id/checkbox" android:layout_width="match_parent" android:layout_height="wrap_content"/> </RelativeLayout> 

Para agrupar los datos que queremos mostrar en cada elemento del ListView escribimos una nueva clase que contiene los datos en campos privados y getters y setters para obtener y establecer esos datos. Tales clases se llaman generalmente modelos de la visión. Un modelo de vista para un diseño como el anterior podría ser algo como esto:

 public class ExampleViewModel { // This is the text which will be set to the CheckBox private String text; // This boolean will be used to save the checked state of the CheckBox private boolean checked; public String getText() { return text; } public void setText(String text) { this.text = text; } public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; } } 

Cada instancia de dicho modelo de vista representaría un elemento en el ListView . Cuando uno de los elementos entra en el área visible del ListView tiene que estar enlazado a una View en el ListView (Eso es algo que tenemos que implementar en getView() del Adapter ). Mientras el elemento esté visible, el modelo permanecerá vinculado a éste, pero tan pronto como la View haya salido del área visible del ListView , será reciclado y enlazado a un modelo de vista diferente que acaba de entrar en el área visible. Esto se denomina reciclado de vistas y se realiza para minimizar la huella de memoria del ListView y para aumentar el rendimiento y la fluidez general del desplazamiento. Views son objetos muy costosos, sobre todo inflando Views y findViewById() cuestan un montón de rendimiento y el punto de vista principal de reciclaje es que usted tiene que inflar sólo un pequeño número de Views una vez que puede reutilizarse y por lo tanto evitar la inflación costosa y findViewById() más adelante.

La mayor parte de lo que he explicado anteriormente sucede automáticamente. Lo que usted como desarrollador tiene que hacer es inflar las Views correctas en getView() o reutilizarlas si ya hay una disponible y luego enlazar el modelo de vista correcto a la View . Sé que la mayor parte de esto parece bastante complicado y confuso si usted primero oye hablar de él, pero consigue mucho más simple y más obvio una vez que comenzamos a mirar el código.

Así que ahora tenemos el diseño de los elementos de vista en el ListView y el modelo de vista para ir junto con él. Lo que necesitamos hacer ahora es escribir otra clase que usualmente se llama titulares de vista. Estos titulares de vista son esencialmente clases contenedor alrededor de las vistas en el ListView . Cada titular de vista contiene una View asociada a un elemento en el ListView y también se ocupan de vincular los datos del modelo de vista a la View . Sin más preámbulo aquí es un titular de vista para ir junto con el modelo de vista desde arriba:

 public class ExampleViewHolder { // The reference to the CheckBox is saved so we only have to perform the findViewById() once. private final CheckBox checkBox; // A reference to the view model which is currently bound to this view holder private ExampleViewModel currentModel; // The View associated with this view holder is passed into the constructor from the Adapter. public ExampleViewHolder(View view) { // And here we look for all relevant views // In our case we just need the CheckBox this.checkBox = (CheckBox) view.findViewById(R.id.checkbox); } public void bind(ExampleViewModel viewModel) { // Unset the listener in case there was one from a previous view model this.checkBox.setOnCheckedChangeListener(null); // Save a reference to the view model which is currently bound to this view holder this.currentModel = viewModel; // Bind the data to the CheckBox this.checkBox.setText(viewModel.getText()); this.checkBox.setChecked(viewModel.isChecked()); // Reset the listener this.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { currentModel.setChecked(isChecked); } }); } } 

Ahora estamos casi terminados. Lo único que falta ahora es conectar todo esto en el Adapter :

 public class ExampleAdapter extends BaseAdapter { // Each type of view in the `ListView` gets its own id // In this example we only have one type of View so we only need one id private static final int EXAMPLE_VIEW_ID = 0; // The default view id is just a fallback private static final int DEFAULT_VIEW_ID = EXAMPLE_VIEW_ID; private final LayoutInflater inflater; private List<ExampleViewModel> viewModels; public ExampleAdapter(Context context, List<ExampleViewModel> viewModels) { // The view models are initially passed in through the constructor. // You can pass an empty list into the Adapter if there is not data initially. this.viewModels = viewModels; this.inflater = LayoutInflater.from(context); } @Override public int getCount() { if(viewModels == null) { return 0; } return viewModels.size(); } @Override public Object getItem(int position) { return viewModels.get(position); } @Override public long getItemId(int position) { final Object model = getItem(position); // Here we check if the model is an instance of ExampleViewModel and if yes we return its id if(model instanceof ExampleViewModel) { return EXAMPLE_VIEW_ID; } return DEFAULT_VIEW_ID; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(getItemId(position) == EXAMPLE_VIEW_ID) { final ExampleViewModel model = (ExampleViewModel) getItem(position); final ExampleViewHolder viewHolder; // If the convertView is null we need to inflate a new view if(convertView == null) { final View view = this.inflater.inflate(ExampleViewHolder.LAYOUT, parent, false); viewHolder = new ExampleViewHolder(view); // Here we set the viewHolder as tag to the View // This is done so we can reuse the same view holder later on // Essentially this is the integral part of the whole view recycling process view.setTag(viewHolder); } else { // If the convertView is not null we can just get the view holder with getTag() from the View viewHolder = (ExampleViewHolder) convertView.getTag(); } // And we just need to bind the model to the view holder viewHolder.bind(model); } return convertView; } } 

Y eso es todo lo que necesitas. Esto es prácticamente una implementación de las mejores prácticas de un Adapter . Si se trata de dos o más tipos diferentes de vistas, debe escribir un modelo de vista y una clase de titular de vista para cada tipo. Usted puede escribir una interfaz llamada ViewModel que se vería algo como esto:

 public interface ViewModel { } 

Cada modelo de vista suyo debe implementar esta interfaz. A continuación, puede utilizar List<ViewModel> en el Adapter que puede contener todos los diferentes tipos de modelos de vista.

 public class TypeOneViewModel implements ViewModel { } public class TypeTwoViewModel implements ViewModel { } 

Tan pronto como todos tus modelos de vista implementen esta interfaz, puedes hacer esto:

 final List<ViewModel> models = new ArrayList<ViewModel>(); models.add(new TypeOneViewModel()); models.add(new TypeTwoViewModel()); ... 

Y esta List que ahora contiene varios tipos diferentes de modelos de vista se puede pasar al Adapter . El Adapter se vería así:

 public class ExampleAdapter extends BaseAdapter { private static final int TYPE_ONE_VIEW_ID = 0; private static final int TYPE_TWO_VIEW_ID = 1; private static final int DEFAULT_VIEW_ID = TYPE_ONE_VIEW_ID; private final LayoutInflater inflater; private List<ViewModel> viewModels; public ExampleAdapter(Context context, List<ViewModel> viewModels) { this.viewModels = viewModels; this.inflater = LayoutInflater.from(context); } @Override public int getCount() { if(viewModels == null) { return 0; } return viewModels.size(); } @Override public ViewModel getItem(int position) { return viewModels.get(position); } @Override public long getItemId(int position) { final ViewModel model = getItem(position); if(model instanceof TypeOneViewModel) { return TYPE_ONE_VIEW_ID; } if(model instanceof TypeTwoViewModel) { return TYPE_TWO_VIEW_ID; } return DEFAULT_VIEW_ID; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(getItemId(position) == TYPE_ONE_VIEW_ID) { final TypeOneViewModel model = (TypeOneViewModel) getItem(position); final TypeOneViewHolder viewHolder; if(convertView == null) { final View view = this.inflater.inflate(TypeOneViewHolder.LAYOUT, parent, false); viewHolder = new TypeOneViewHolder(view); view.setTag(viewHolder); } else { viewHolder = (TypeOneViewHolder) convertView.getTag(); } viewHolder.bind(model); } if(getItemId(position) == TYPE_TWO_VIEW_ID) { final TypeTwoViewModel model = (TypeTwoViewModel) getItem(position); final TypeTwoViewHolder viewHolder; if(convertView == null) { final View view = this.inflater.inflate(TypeTwoViewHolder.LAYOUT, parent, false); viewHolder = new TypeTwoViewHolder(view); view.setTag(viewHolder); } else { viewHolder = (TypeTwoViewHolder) convertView.getTag(); } viewHolder.bind(model); } return convertView; } } 

También puede unificar a sus titulares de opinión escribiendo una clase abstracta. Una clase tan abstracta parecería algo así:

 public abstract class ViewHolder<T extends ViewModel> { protected final View itemView; public ViewHolder(View view) { this.itemView = view; } public abstract void bind(T model); } 

Si utiliza esta clase abstracta como clase base para sus titulares de vista, escribiría así:

 public class TypeOneViewHolder extends ViewHolder<TypeOneViewModel> { public TypeOneViewHolder(View view) { super(view); ... } public void bind(TypeOneViewModel model) { ... } } 

Aunque esta parte no es realmente necesario. La parte más importante al tratar con múltiples tipos diferentes de elementos en el ListView es que todos los modelos implementan una interfaz común para que pueda ponerlos en forma segura en la misma List .

De todos modos, todo esto parece mucho más simple y más limpio que tu Adapter , ¿no? De esta manera usted tiene la separación perfecta entre los datos en el ListView y las Views que exhiben los datos y es mucho más maintainable. Usted puede fácilmente implementar animaciones en el titular de la vista sin tener que preocuparse de la vista de reciclaje y un montón de requisitos se vuelven mucho más fácil de implementar. El RecyclerView lleva todo esto al siguiente nivel de curso. Funciona de la misma manera, pero tiene varias mejoras importantes sobre el ListView , realmente sugiero que eche un vistazo a él.


Una cosa que me olvidé completamente: puede exponer la List interna de modelos de vista con un getter para que pueda modificar los modelos de vista desde el exterior. Agregue métodos como este al Adapter :

 public List<ExampleViewModel> viewmodels() { return viewModels; } public void setViewModels(List<ExampleViewModel> models) { viewModels = models; } 

A continuación, puede modificar los modelos de vista en el Adapter esta manera:

 adapter.setViewModels(newData); ... adapter.viewmodels().add(viewModel); 

Y cuando haya terminado de modificar los datos, puede actualizar ListView llamando a notifyDataSetChanged() en el Adapter .

  • Youtube Video no se muestra en RecyclerView
  • RecyclerView no se desinscribe del adaptador en el cambio de orientación
  • Problemas para recrear listas de diseño de materiales con licencia en Android
  • Disposición de Android: Recyclerview horizontal dentro de una Recyclerview vertical dentro de un Viewpager con comportamientos de desplazamiento
  • SetAlpha () en onBindView en RecyclerView no funciona en la primera pantalla
  • Cómo obligar a RecyclerView a desplazarse cuando no hay suficientes elementos para llenar la altura de la sceen
  • GetAdapterPosition () no devuelve la posición del elemento en RecyclerView
  • ¿La transición reversa reversa del elemento del androide sobre detrás después de cambio de la orientación?
  • ¿Hay alguna forma de habilitar las barras de desplazamiento para RecyclerView en código?
  • Android: Actualización de recyclerview con nuevos datos
  • Aplicación android de Gmail como vista de lista con una letra en el icono
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.