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.
- Cómo actualizar ListView en fragmento que se rellena con BaseAdapter?
- Elemento de lista que se repite en android personalizado listview
- Agregar el botón favorito en la vista de lista
- Adaptador con hasStableIds utilizando GUID o Strings como Ids
- notifyDataSetChanges para el adaptador de lista, modifique sólo parte de la vista
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
- ListView setText en una posición determinada
- Listview en SwipeRefreshLayout - no muestra nada cuando el adaptador personalizado tiene sólo una fila para mostrar
- Android: la imagen en el listview parpadea mientras llama a notifyDataSetChanged en algunos dispositivos
- OnItemClickListener no funciona correctamente cuando el adaptador contiene un botón con onClickListener
- ¿Cuál es la diferencia entre BaseAdapter y ArrayAdapter?
- ¿No puede obtener valores de EditViewText de ListView?
- Cómo agregar datos a BaseAdapter personalizado para listView - Android
- ListView muestra sólo un elemento
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; }
- Longitud máxima del método PutExtra de Intención? (Cierre de la fuerza)
- ¿Cómo reiniciar automáticamente un servicio incluso si la fuerza del usuario lo cierra?