2014-11-02 3 views
10

Ich dachte, sie wären gleich, aber sie sind es nicht. Der folgende Code gibt eine IndexOutOfBounds Ausnahme, wenn ich versuche, den „Position“ Index meines Datensatz zuzugreifen, in diesem Fall eine Liste eines Modells I Aufgabe genannt erstellt:Wie hängt die Position eines RecyclerView-Adapters mit dem Index seines Datensatzes zusammen?

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

Löschen von meinem recyclerview manchmal unerwarteten Ergebnissen. Manchmal wird das Element vor dem angeklickten Element gelöscht, manchmal kommt eine indexOutOfBounds-Ausnahme bei "taskList.get (position)" vor.

Lesen https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html und https://developer.android.com/training/material/lists-cards.html gab mir keinen weiteren Einblick, warum dies geschah und wie es zu beheben ist.

Es sieht so aus, als ob RecyclerView die Zeilen recycelt, aber ich würde nicht erwarten, dass eine indexoutofbounds -Ausnahme eine kleinere Teilmenge von Zahlen verwendet, um meine Liste zu indizieren.

+0

Entschuldigung, ich habe Ihre ganze Frage nicht gelesen, aber in Ihrem Code denke ich, der zweite Parameter von onCreateViewHolder bezieht sich nicht auf die Position des Elements in Adapter, aber die "ViewType", die ViewHolder erstellt wird. Lesen Sie die Referenz für weitere Details :) –

+0

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder (android.view.ViewGroup,%20int) Hier ist, wo es ist verwandte Methoden sind definiert. Übrigens sollten Sie Ihr Aufgabenobjekt nur in der onBindViewHolder-Methode erhalten: -? –

Antwort

10

RecyclerView bondet keine Ansichten neu, wenn sich ihre Positionen ändern (aus offensichtlichen Leistungsgründen). Zum Beispiel, wenn Ihr Datensatz wie folgt aussieht:

A B C D 

und Sie Artikel X über

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

zu bekommen

A X B C D 

RecyclerView hinzufügen, werden nur X binden und die Animation laufen .

Es gibt eine getPosition-Methode in ViewHolder, die jedoch nicht mit der Adapterposition übereinstimmt, wenn Sie sie mitten in einer Animation aufrufen.

Wenn Sie die Adapterposition benötigen, ist die sicherste Option, die Position vom Adapter zu erhalten.

Update für Ihren Kommentar

ein Task-Feld auf den ViewHolder hinzufügen.

Ändern Sie onCreateViewHolder wie folgt, um zu vermeiden, dass bei jedem Rebind ein Listener-Objekt erstellt wird.

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

so in Ihre auf bind Methode, tun Sie

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

Ich möchte ein Element meines Adapters löschen, wenn ich darauf klicke, wie in meinem Adapter gezeigt. pos ist nicht die zu verwendende Variable, welche Variable soll ich verwenden? –

+0

Legen Sie die Aufgabe in Ihren TaskViewHolder, damit Sie im Klicklistener darauf zugreifen können. Erstellen Sie auch nicht jedes Mal einen Klick-Listener, wenn View neu gebündelt ist, sondern weisen Sie ihn beim Erstellen zu. Wird meine Antwort entsprechend aktualisieren. – yigit

+0

danke, aber ich habe ein Getter und Setter für eine geschützte Feld Aufgabe in der Viewholder, kopierte und klebte den Code in diesem Beitrag, und das Löschen tut nichts –

2

Persönlich bin ich dieses Konzept der RecyclerViews nicht mögen. Scheint so, als wäre es nicht komplett gedacht worden.

Wie beim Entfernen eines Artikels gesagt wurde, blendet die Recycler-Ansicht nur ein Element aus. Normalerweise möchten Sie diesen Artikel jedoch nicht in Ihrer Sammlung belassen. Wenn ein Element aus der Sammlung gelöscht wird, "verschiebt es seine Elemente in Richtung 0", während RecyclerView die gleiche Größe behält.

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

Warum nicht Sie eine öffentliche Schnittstelle in der MainActivity für die Schaltfläche klicken und controle die Aktion verwenden:

Wenn Sie taskList.remove(position); Ihre Position anrufen, muss erneut bewertet werden.

In Ihrem Adapter add:

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

und

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 der Anruf in der ViewHolder Klasse

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

In Ihrem MainActivity Ihren Adapter ändern, den Anruf zu handhaben

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

sollte dies angenommen werden Antwort –

+0

danke Sie sparen meine Zeit –

8

Wie yigit sagte, arbeitet RecyclerView wie folgt aus:

A B C D 

und Sie Artikel X über

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

Sie erhalten

A X B C D 

Mit holder.getAdapterPosition() in OnClickListener() hinzufügen, geben Sie müssen das richtige Element aus dem zu entfernenden Datensatz entfernen, nicht die "statische" Ansichtsposition. Hier ist das Dokument darüber onBindViewHolder

+1

Dies sollte definitiv die akzeptierte Antwort sein. Schnell, einfach und effektiv. –

2

Dank @yigit für seine Antwort, seine Lösung funktionierte hauptsächlich, ich habe es nur ein wenig so zwickt, um vh.getTask() zu vermeiden, die ich nicht sicher war, wie zu implementieren.

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

     } 
    }); 

Hinweis stattdessen die aktualisierte Position zu erhalten, können Sie vh.getAdapterPosition() aufrufen, die die Linie ist, dass Sie die aktualisierte Position von der zugrunde liegenden Datenmenge anstatt die gefälschte Ansicht geben.

Dies funktioniert für mich ab sofort, wenn jemand von einem Nachteil bei der Verwendung weiß, lassen Sie es mich bitte wissen. Hoffe, das hilft jemandem.