2014-11-02 18 views
10

Myślałem, że są takie same, ale nie są. Poniższy kod daje wyjątek indexOutOfBounds przy próbie uzyskania dostępu do „Pozycja” indeks mojego zbioru danych, w tym przypadku wykaz modelu stworzyłem zwanego zadanie:Jaka jest pozycja adaptera RecyclerView powiązanego z indeksem jego zestawu danych?

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> { 

private List<Task> taskList; 
private TaskAdapter thisAdapter = this; 

// cache of views to reduce number of findViewById calls 
public static class TaskViewHolder extends RecyclerView.ViewHolder { 
    protected TextView taskTV; 
    protected ImageView closeBtn; 

    public TaskViewHolder(View v) { 
     super(v); 
     taskTV = (TextView)v.findViewById(R.id.taskDesc); 
     closeBtn = (ImageView)v.findViewById(R.id.xImg); 
    } 
} 


public TaskAdapter(List<Task> tasks) { 
    if(tasks == null) 
     throw new IllegalArgumentException("tasks cannot be null"); 
    taskList = tasks; 
} 


// onBindViewHolder binds a model to a viewholder 
@Override 
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) { 
    final int position = pos; 
    Task currTask = taskList.get(pos); 
    taskViewHolder.taskTV.setText(currTask.getDescription()); 

    **taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() { 
     @Override 
     public void onClick(View v) { 
      Log.d("TRACE", "Closing task at position " + position); 
      // delete from SQLite DB 
      Task taskToDel = taskList.get(position); 
      taskToDel.delete(); 
      // updating UI 
      taskList.remove(position); 
      thisAdapter.notifyItemRemoved(position); 
     } 
    });** 
} 

@Override 
public int getItemCount() { 
    //Log.d("TRACE", taskList.size() + " tasks in DB"); 
    return taskList.size(); 
} 


// inflates row to create a viewHolder 
@Override 
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) { 
    View itemView = LayoutInflater.from(parent.getContext()). 
            inflate(R.layout.list_item, parent, false); 
    Task currTask = taskList.get(pos); 

    //itemView.setBackgroundColor(Color.parseColor(currTask.getColor())); 
    return new TaskViewHolder(itemView); 
} 
} 

Usuwanie z moim recyclerview daje nieoczekiwane rezultaty czasami. Czasami element przed jednym kliknięciem jest usuwany, innym razem wyjątek indexOutOfBounds występuje w "taskList.get (position)".

Czytanie https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html i https://developer.android.com/training/material/lists-cards.html nie dało mi lepszego wglądu w to, dlaczego tak się dzieje i jak to naprawić.

Wygląda na to, że RecyclerView przetwarza wiersze, ale nie spodziewałbym się wyjątku indexupofound przy użyciu mniejszego podzbioru liczb w celu indeksowania mojej listy.

+0

Niestety nie przeczytałem całego twojego pytania, ale w twoim kodzie, myślę, że drugi parametr onCreateViewHolder nie odnosi się do pozycji elementu w adapterze, ale pojawi się "viewType", który jest tworzony ViewHolder. Przeczytaj referencję, aby uzyskać więcej szczegółów :) –

+0

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup,%20int) Oto, gdzie jest zdefiniowano powiązaną metodę. Btw powinieneś dostać swój element zadania tylko w tylko onBindViewHolder: -? –

Odpowiedz

10

Widok RecyclerView nie wiąże ponownie widoków po zmianie ich położenia (z oczywistych powodów związanych z wydajnością). Na przykład, jeżeli zestaw danych wygląda następująco:

A B C D 

i dodać artykuł X poprzez

mItems.add(1, X); 
notifyItemInserted(1, 1); 

dostać

A X B C D 

RecyclerView tylko wiązania X i uruchomić animację .

Istnieje metoda getPosition w ViewHolder, ale może nie być zgodna z pozycją adaptera, jeśli wywołasz ją w środku animacji.

Jeśli potrzebujesz pozycji adaptera, najbezpieczniejszą opcją jest uzyskanie pozycji z adaptera.

aktualizacja dla Twojego komentarza

Dodawanie pola zadań do ViewHolder.

Zmień onCreateViewHolder w następujący sposób, aby uniknąć tworzenia obiektu detektora dla każdego ponownego wiązania.

// inflates row to create a viewHolder 
@Override 
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int type) { 
    View itemView = LayoutInflater.from(parent.getContext()). 
           inflate(R.layout.list_item, parent, false); 

    final TaskViewHolder vh = new TaskViewHolder(itemView); 
    taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() { 
     @Override 
     public void onClick(View v) { 
      // delete from SQLite DB 
      Task taskToDel = vh.getTask(); 
      final int pos = taskList.indexOf(taskToDel); 
      if (pos == -1) return; 
      taskToDel.delete(); 
      // updating UI 
      taskList.remove(pos); 
      thisAdapter.notifyItemRemoved(pos); 
     } 
    }); 
} 

więc w sprawie metody bind, robisz

// onBindViewHolder binds a model to a viewholder 
@Override 
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) { 
    Task currTask = taskList.get(pos); 
    taskViewHolder.setTask(currTask); 
    taskViewHolder.taskTV.setText(currTask.getDescription()); 
} 
+2

Chcę usunąć element adaptera po jego kliknięciu, jak pokazano w moim adapterze. POS nie jest zmienną do użycia, jakiej zmiennej powinienem użyć? –

+0

włóż zadanie do swojego TaskViewHolder, aby uzyskać do niego dostęp w detektorze kliknięć. Nie należy także tworzyć odbiorników kliknięć za każdym razem, gdy Widok jest odbijany, zamiast tego należy go przypisać przy tworzeniu. Odpowiednio zaktualizuje moją odpowiedź. – yigit

+0

dziękuję, ale dodałem program pobierający i ustawiający dla chronionego zadania pola w oknie podglądu, skopiowałem i wkleiłem kod w tym poście, a usunięcie nic nie robi –

2

Osobiście nie lubię tej koncepcji RecyclerViews. Wydaje się, że nie zostało to całkowicie pomyślane.

Tak jak powiedziano przy wyjmowaniu przedmiotu, widok Recyklera po prostu ukrywa przedmiot. Zazwyczaj jednak nie chcesz zostawiać tego przedmiotu w swojej kolekcji. Podczas usuwania elementu z kolekcji "przesuwa elementy w kierunku 0", podczas gdy recyclerView zachowuje ten sam rozmiar.

Jeśli dzwonisz taskList.remove(position); swoją pozycję muszą być ocenione ponownie:

int position = recyclerView.getChildAdapterPosition(taskViewHolder.itemView); 
4

Dlaczego nie używasz interfejsu publicznego na przycisk kliknięcia i controle działania w główną działalność.

W swoim dodatku adaptera:

public interface OnItemClickListener { 
    void onItemClick(View view, int position, List<Task> mTaskList); 
} 

i

public OnItemClickListener mItemClickListener; 

// Provide a suitable constructor (depends on the kind of dataset) 
public TaskAdapter (List<Task> myDataset, OnItemClickListener mItemClickListener) { 
    this.mItemClickListener = mItemClickListener; 
    this.mDataset = mDataset; 
} 

plus rozmowy w klasie ViewHolder

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 

    public ViewHolder(View v) { 
     super(v); 
     ... 
     closeBtn = (ImageView)v.findViewById(R.id.xImg); 
     closeBtn.setOnClickListener(this); 
    } 

    @Override 
    public void onClick(View v) { 
     // If not long clicked, pass last variable as false. 
     mItemClickListener.onItemClick(v, getAdapterPosition(), mDataset); 
    } 
} 

W swojej główną działalność zmienić swoją kartę do obsługi połączenia

// set Adapter 
    mAdapter = new TaskAdapter(taskList, new TaskAdapter.OnItemClickListener() { 

     @Override 
     public void onItemClick(View v, int position) { 
      if (v.getId() == R.id.xImg) { 
       Task taskToDel = taskList.get(position); 
       // updating UI 
       taskList.remove(position); 
       thisAdapter.notifyItemRemoved(position); 
       // remove from db with unique id to use delete query 
       // dont use the position but something like taskToDel.getId() 
       taskToDel.delete(); 
      } 
     } 
    }); 
+0

to należy zaakceptować odpowiedź –

+0

dzięki, że oszczędzasz mój czas –

8

Jak Yigit powiedział RecyclerView działa tak:

A B C D 

i dodać artykuł X poprzez

mItems.add(1, X); 
notifyItemInserted(1, 1); 

masz

A X B C D 

Korzystanie holder.getAdapterPosition() w onClickListener() da możesz usunąć właściwy element z zestawu danych, a nie "statyczną" pozycję widoku. Oto dokument na ten temat: onBindViewHolder

+1

To zdecydowanie powinna być akceptowana odpowiedź. Szybki, prosty i skuteczny. –

2

Dzięki @yigit za odpowiedź, jego rozwiązanie głównie działało, po prostu zmieniłem go trochę, aby uniknąć używania vh.getTask(), którego nie byłem pewien, jak zaimplementować.

final ViewHolder vh = new ViewHolder(customView); 
    final KittyAdapter final_copy_of_this = this; 

    // We attach a CheckChange Listener here instead of onBindViewHolder 
    // to avoid creating a listener object on each rebind 
    // Note Rebind is only called if animation must be called on view (for efficiency) 
    // It does not call on the removed if the last item is checked 
    vh.done.setChecked(false); 
    vh.done.setOnCheckedChangeListener(null); 
    vh.done.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 
     @Override 
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 
      buttonView.setEnabled(false); 
      final int pos2 = vh.getAdapterPosition(); // THIS IS HOW TO GET THE UPDATED POSITION 

      // YOU MUST UPDATE THE DATABASE, removed by Title 
      DatabaseHandler db = new DatabaseHandler(mContext); 
      db.remove(mDataSet.get(pos2).getTitle(), fp); 
      db.close(); 
      // Update UI 
      mDataSet.remove(pos2); 
      final_copy_of_this.notifyItemRemoved(pos2); 

     } 
    }); 

Wskazówka zamiast uzyskać zaktualizowane stanowisko, można zadzwonić vh.getAdapterPosition(), która to linia, która daje zaktualizowane stanowisko z bazowego zestawu danych zamiast fałszywego widzenia.

To działa dla mnie od teraz, jeśli ktoś wie o wadach korzystania z tego proszę daj mi znać. Mam nadzieję, że to pomaga komuś.

Powiązane problemy