2013-05-09 10 views
21

Znalazłem tajemniczy problem, który może być błędem! Mam listę w moim fragmencie. Każdy rząd ma przycisk. Lista nie powinna odpowiadać na kliknięcia, jednak przyciski są klikalne.Android, List Adapter zwraca błędną pozycję w getView

Aby uzyskać kliknięcie przycisku, utworzyłem detektor i zaimplementowałem go w moim fragmencie. To jest kod mojego adaptera.

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

Kiedy uruchomić aplikację, widzę moje wiersze, jednak ponieważ moja lista jest długa i zawiera ponad 200 pozycji, kiedy goto środku listy, a następnie kliknij element, a następnie powrócił pozycja jest źle (to coś jak 7, czasami 4 i itp.).

Teraz co to jest tajemnica? Jeśli aktywuję element listener listy z mojego fragmentu i kliknę wiersz, wtedy poprawna pozycja wiersza zostanie wyświetlona w tym wierszu, jeśli kliknę przycisk, wtedy wyświetlona zostanie niewłaściwa pozycja.

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

Wynik w LogCat:

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

Wszelkie sugestie będą mile widziane. Dzięki

+0

Gdzie implementujesz 'OnAddUserClicked()'? Również jeśli używasz BaseAdaptera i 'List ' możesz uprościć swój adapter używając 'ArrayAdapter', ponieważ jest on w zasadzie _is a_' List' i jest zbudowany tak, aby rozwiązać dokładny problem z ładowaniem tablicy lub listy obiektów danych do AdapterView – FoamyGuy

Odpowiedz

46

Ponieważ posiadacz convertView i zostanie poddane recyklingowi w użyciu, przenieść setOnClickListener z 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)); 
      } 
     }); 

Nie jest to najlepsze rozwiązanie, bo nie będzie jakiś problem z wydajnością. Sugeruję, aby utworzyć mapę dla widoku i utworzyć nowy widok dla elementu, a następnie po prostu użyć względnego widoku dla każdego widoku.

myślę, że będzie to lepsze rozwiązanie z najlepszą wydajność:

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

Można również zarządzać widok przez siebie. Utwórz każdy unikalny widok dla swojego przedmiotu, nie wyświetlaj go ponownie.

//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

Nie sądzę, że miejsce ustawienia detektora kliknięć będzie miało znaczenie, ponieważ powinno uzyskać różne wartości dla 'pozycji' to kolejne wywołania. I tak będzie robić to samo, po prostu przechodząc wzdłuż innego obiektu użytkownika w oparciu o 'position', która została przekazana do getView(). – FoamyGuy

+0

Dokładnie, buptcode ma rację.Kiedy go przeniosłem, zarówno słuchacz listy przedmiotów, jak i mój słuchacz zwrócił tę samą pozycję. Jeszcze raz dziękuję :) Proszę dać mi próbkę, jeśli masz o swojej sugestii. – Hesam

+0

Ponieważ zapisujemy ustawioną pozycję w instrukcjach 'if (convertView == null) {', więc pozycja nie zostanie ustawiona, jeśli 'convertView' nie ma wartości null. stanie się to za każdym razem, gdy listview będzie przetwarzał konwersję. W przeciwnym razie musimy ustawić ustawioną pozycję z instrukcji else, abyśmy mogli uzyskać właściwą pozycję. – buptcoder

2

Czy próbowałeś zrobić coś takiego:

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

a następnie pobrać Wich wiersz został kliknięty w zwrotnego dla przycisku, tak:

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

Dziwne, że po trzech latach i dwóch podobnych decyzjach jest to pierwsze rozwiązanie tej odpowiedzi. Jest to również uniwersalne rozwiązanie, ponieważ możemy utworzyć jeden obiekt 'onClickListener = new View.OnClickListener (...)' i przypisać go do dowolnego przycisku (jeśli jest ich wiele). – CoolMind

1

Innym podejściem znalazłem użyteczne (jeśli oczywiście korzystasz z wzorca ViewHolder), ustaw indeks na oddzielnym atrybucie za każdym razem, gdy wywoływany jest getView(), następnie w twoim onClickListener musisz po prostu odwołać się do twojego właściciela po sition atrybut, coś w tym stylu:

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

} 
Powiązane problemy