2013-05-09 12 views
21

Ho trovato un problema misterioso che potrebbe essere un bug! Ho una lista nel mio frammento. Ogni riga ha un pulsante. L'elenco non dovrebbe rispondere al clic, tuttavia i pulsanti sono selezionabili.Android, List Adapter restituisce la posizione errata in getView

Per ottenere quale pulsante ha fatto clic ho creato un listener e lo implemento nel mio frammento. Questo è il codice del mio adattatore.

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; 
    } 
} 

Quando eseguo l'applicazione, posso vedere le mie righe tuttavia dato che la mia lista è lunga e ha più di 200 articoli quando ho goto mezzo di lista e clic su un elemento poi restituito la posizione è sbagliata (è qualcosa come 7, a volte 4 e ecc.).

Ora qual è il mistero? Se attivo su listener di elementi della lista dal mio frammento e faccio clic sulla riga, verrà visualizzata la posizione della riga corretta mentre su quella riga se faccio clic sul pulsante, verrà visualizzata la posizione errata.

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

Risultato in logcat:

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

Ogni suggerimento sarebbe apprezzato. Grazie

+0

Dove stai implementando 'OnAddUserClicked()'? Inoltre, se si utilizza un BaseAdapter e l''Elenco ', è possibile semplificare l'adattatore usando' ArrayAdapter', poiché essenzialmente è a_ 'List' ed è costruito per risolvere il problema esatto del caricamento di una matrice o di un elenco di oggetti di dati in un AdapterView – FoamyGuy

risposta

46

Poiché il titolare convertView e sarà riciclato da utilizzare, spostare il setOnClickListener fuori 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)); 
      } 
     }); 

Non è la soluzione migliore per questo, perché ci sarà qualche problema di prestazioni. Ti suggerisco di creare una mappa per la tua vista e creare una nuova vista per il tuo articolo, quindi basta usare la vista relativa per ogni vista.

penso che sarà una soluzione migliore con la migliore prestazione:

@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; 
} 

è possibile anche gestire la visualizzazione da soli. Crea ogni vista unica per il tuo oggetto, non riciclare la 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; 
} 
+0

Non penso che dove si imposta il listener dei clic sarà importante dal momento che dovrebbe ottenere valori diversi per 'position' è le chiamate successive. E quindi farà la stessa cosa solo passando un oggetto utente diverso basato sulla 'posizione' che è stata passata a getView(). – FoamyGuy

+0

Esatto, Buptcoder ha ragione.Quando l'ho spostato, sia l'ascoltatore dell'elenco che il mio ascoltatore hanno restituito la stessa posizione. Grazie ancora :) Per favore datemi un esempio se avete il vostro suggerimento. – Hesam

+0

Poiché scriviamo la posizione impostata nelle istruzioni 'if (convertView == null) {', quindi la posizione non verrà impostata se 'convertView' non è nullo. succederà ogni volta che listview ricicla il convertview. Per evitare che abbiamo bisogno di mettere la posizione impostata fuori dalla dichiarazione è else in modo che possiamo ottenere la giusta posizione. – buptcoder

2

Forse si è tentato di fare qualcosa di simile:

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

e quindi recuperare fila goduto era cliccato nel callback per il pulsante, in questo modo:

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

Strano, che dopo tre anni e due decisioni simili è il primo upvote per questa risposta. Anche questa è una soluzione universale, poiché possiamo creare un oggetto 'onClickListener = new View.OnClickListener (...)' e assegnarlo a qualsiasi pulsante (se molti). – CoolMind

1

Un altro approccio che ho trovato utile (se si utilizza il pattern ViewHolder ovviamente) è impostare l'indice su un attributo separato ogni volta che viene chiamato getView(), quindi all'interno di onClickListener è sufficiente fare riferimento al po attributo sition, qualcosa del genere:

@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; 

} 
Problemi correlati