Android, el adaptador de lista devuelve una posición incorrecta en getView

He encontrado un misterioso problema que puede ser un error! Tengo una lista en mi fragmento. Cada fila tiene un botón. La lista no debe responder al clic, pero los botones son clickable.

Para conseguir qué botón ha hecho clic he creado un oyente e implementarlo en mi fragmento. Este es el código de mi adaptador.

public class AddFriendsAdapter extends BaseAdapter { public interface OnAddFriendsListener { public void OnAddUserClicked(MutualFriends user); } private final String TAG = "*** AddFriendsAdapter ***"; private Context context; private OnAddFriendsListener listener; private LayoutInflater myInflater; private ImageDownloader imageDownloader; private List<MutualFriends> userList; public AddFriendsAdapter(Context context) { this.context = context; myInflater = LayoutInflater.from(context); imageDownloader = ImageDownloader.getInstance(context); } public void setData(List<MutualFriends> userList) { this.userList = userList; Log.i(TAG, "List passed to the adapter."); } @Override public int getCount() { try { return userList.size(); } catch (Exception e) { e.printStackTrace(); return 0; } } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = myInflater.inflate(R.layout.list_add_friends_row, null); holder = new ViewHolder(); Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf"); holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName); holder.tvUserName.setTypeface(font); holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture); holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd); holder.btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, "Item: " + position); listener.OnAddUserClicked(userList.get(position)); } }); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvUserName.setText(userList.get(position).getName()); imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl()); return convertView; } public void setOnAddClickedListener(OnAddFriendsListener listener) { this.listener = listener; } static class ViewHolder { TextView tvUserName; ImageView ivPicture; Button btnAdd; } } 

Cuando ejecuto la aplicación, puedo ver mis filas sin embargo, ya que mi lista es larga y tiene más de 200 elementos cuando i goto medio de la lista y haga clic en un elemento, entonces la posición devuelta es incorrecta (es algo como 7, a veces 4 y etc).

Ahora, ¿cuál es el misterio? Si activo en el listener del elemento de la lista de mi fragmento y hago clic en la fila entonces la posición correcta de la fila será exhibida mientras que en esa fila si hago clic en el botón entonces la posición incorrecta será exhibida.

 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.e(TAG, "item " + position + " clicked."); } }); 

Resultado en logcat:

 05-09 10:22:25.228: E/AddFriendsFragment(20296): item 109 clicked. 05-09 10:22:34.453: E/*** AddFriendsAdapter ***(20296): Item: 0 

Cualquier sugerencia sería apreciada. Gracias

Debido a que el convertView y el soporte se reciclarán para usarlo, mueva su setOnClickListener de la sentencia if else:

  if (convertView == null) { convertView = myInflater.inflate(R.layout.list_add_friends_row, null); holder = new ViewHolder(); Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf"); holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName); holder.tvUserName.setTypeface(font); holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture); holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) Log.e(TAG, "Item: " + position); listener.OnAddUserClicked(userList.get(position)); } }); 

No es la mejor solución para eso, porque habrá algún problema de rendimiento. Le sugiero que cree un mapa para su vista y cree una nueva vista para su elemento, simplemente use la vista relativa para cada vista.

Creo que será una mejor solución con el mejor rendimiento:

 @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = myInflater.inflate(R.layout.list_add_friends_row, null); holder = new ViewHolder(); Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf"); holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName); holder.tvUserName.setTypeface(font); holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture); holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd); holder.btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer pos = (Integer)v.getTag(); Log.e(TAG, "Item: " + pos); listener.OnAddUserClicked(userList.get(pos)); } }); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvUserName.setText(userList.get(position).getName()); imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl()); holder.btnAdd.setTag(position); return convertView; } 

También puede administrar su vista por sí mismo. Cree cada vista única para su artículo, no recicle vista.

 //member various private Map<Integer, View> myViews = new HashMap<Integer, View>(); @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; View view = myViews.get(position); if (view == null) { view = myInflater.inflate(R.layout.list_add_friends_row, null); //don't need use the holder anymore. Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf"); holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName); holder.tvUserName.setTypeface(font); holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture); holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd); holder.btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer pos = (Integer)v.getTag(); Log.e(TAG, "Item: " + pos); listener.OnAddUserClicked(userList.get(pos)); } }); holder.tvUserName.setText(userList.get(position).getName()); imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl()); myViews.put(position, view); } return view; } 

¿Has intentado hacer algo como esto:

 holder.btnAdd.setTag(Integer.valueOf(position)); 

Y luego recuperar la fila que se hizo clic en la devolución de llamada para el botón, como este:

 public void btnAddClickListener(View view) { position = (Integer)view.getTag(); Foo foo = (Foo)foos_adapter.getItem(position); //get data of row(position) //do some } 

Otro enfoque que me pareció útil (si estás usando el patrón ViewHolder por supuesto) es establecer el índice en un atributo independiente cada vez que se llama a getView (), entonces dentro de tu onClickListener sólo tienes que hacer referencia al atributo de posición de tu titular, algo así:

 @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder holder; if(convertView == null){ convertView = View.inflate(mContext, R.layout.contact_picker_row,null); holder = new ViewHolder(); holder.body = (RelativeLayout)convertView.findViewById(R.id.numberBody); convertView.setTag(holder); }else{ holder = (ViewHolder)convertView.getTag(); } holder.position = position; holder.body.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext,"Clicked on: "+holder.position,Toast.LENGTH_LONG).show(); } }); return convertView; } private class ViewHolder{ RelativeLayout body; int position; } 
  • Cómo llamar a clase de adaptador antes de la actividad
  • ListView con temporizador Coundown. El temporizador parpadea cuando se desplaza la vista de lista
  • Cómo utilizar Dialog dentro de BaseAdapter?
  • Detectar cuando BaseAdapter.notifyDataSetChanged () finalizó
  • Cómo obtener id de un elemento particular de listview en android?
  • Evitar que el adaptador recicle las vistas en desplazamiento
  • Android: Cambia la imagen de un elemento en particular en listview
  • Android: BaseAdapter y getLayoutInflater en un archivo de clase separado
  • EditText comportamiento extraño en ListView BaseAdapter
  • getView sólo se llama al desplazarse en android api nivel 8
  • Cómo restablecer listview después de searchview se cierra en android?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.