Wrong row deleted from custom listview with spinner

拥有回忆 提交于 2019-12-11 03:55:16

问题


I'm working on app that contains a custom ListView with a remove button that I update from the main activity.

I have an issue removing a row from the ListView, although I'm deleting the right index from the custom list view and call notifyDataChanged()method, GUI does not update correctly.

Here I wrote a sample project, like my real one in the idea just more sample:

  • activity_main.xml (main layout) contains ListView only called listview.
  • listview_row.xml contains two Spinners (student name and grade), set and remove button and text view.
  • Student class contains two variables: name (String) and grade (int)

MainActivity.java:

public class MainActivity extends Activity {
    ListView listView;
    listviewAdapter adapter;
    ArrayList<Student> students = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String[] names = new String[]{"Tom", "Ben", "Gil", "Adam", "Moshe", "Adi", "Michael", "Yasmin", "Jessica", "Caroline", "Avi", "Yael"};

        students.add(new Student());
        students.add(new Student());
        students.add(new Student());

        adapter = new listviewAdapter(this, students, names);
        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);

    }

    public void updateStatus(int position)
    {
        View convertView = listView.getChildAt(position - listView.getFirstVisiblePosition());
        TextView tvValue = (TextView) convertView.findViewById(R.id.tv_Value);
        Spinner spName = (Spinner) convertView.findViewById(R.id.spNames);
        Spinner spGrade = (Spinner) convertView.findViewById(R.id.spGrades);

        tvValue.setText(spName.getSelectedItem().toString() + " got " + spGrade.getSelectedItem().toString());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_Add)
        {
            students.add(new Student());
            adapter.notifyDataSetChanged();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

listviewAdapter.java

public class listviewAdapter extends BaseAdapter
{
    public Activity context;
    public LayoutInflater inflater;

    private ArrayList<Student> studentID;
    private String[] studentsNames;

    public listviewAdapter(Activity context, ArrayList<Student> students, String[] names)
    {
        super();
        studentID = students;
        studentsNames = names;

        this.context = context;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        return studentID.size();
    }

    @Override
    public Object getItem(int position) {
        return studentID.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }


    @Override
    public int getViewTypeCount() {
        return studentID.size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    public class ViewHolder {
        Spinner spNames, spGrades;
        TextView tvValue;
        Button btnSet, btnRemove;
    }

    @Override
    public View getView(int i, View view, final ViewGroup viewGroup)
    {
        final ViewHolder holder;
        if (view == null) {
            holder = new ViewHolder();
            view = inflater.inflate(R.layout.listview_row, null);

            holder.spNames = (Spinner) view.findViewById(R.id.spNames);
            holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
            holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
            holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
            holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
            view.setTag(holder);
            holder.spNames.setTag(0);
            holder.spGrades.setTag(0);
        }
        else{
            holder = (ViewHolder) view.getTag();
        }

        // pop spinner names
        ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
        studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spNames.setAdapter(studentsNamesAdapater);

        // pop spinner grades
        String[] grades = new String[101];
        for (int grade = 0; grade < 101; grade++)
            grades[grade] = String.valueOf(grade);

        final ArrayAdapter<String> studentsGradesAdapter = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
        studentsGradesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spGrades.setAdapter(studentsGradesAdapter);


        // select the right spNames index
        holder.spNames.setSelection((Integer) holder.spNames.getTag());
        // saving spinner index
        holder.spNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                holder.spNames.setTag(position);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        // select the right spGrades index
        holder.spGrades.setSelection((Integer) holder.spGrades.getTag());
        // saving spinner index
        holder.spGrades.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                holder.spGrades.setTag(position);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        // set (variable and textview)
        holder.btnSet.setTag(i);
        holder.btnSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // update studentID
                int position = (Integer) v.getTag();
                Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
                studentID.set(position, tmp);
                ((MainActivity) context).updateStatus(position);
            }
        });


        // remove row
        holder.btnRemove.setTag(i);
        holder.btnRemove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = (Integer) v.getTag();
                studentID.remove(position);
                //notifyDataSetChanged();
                ((MainActivity) context).adapter.notifyDataSetChanged();

                // for debug
                String dStatus = "Vector size: " + studentID.size() + "\n";
                for (int index = 0; index < studentID.size(); index++)
                    dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
                Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
            }
        });

        return view;
    }
}

My problem is that the GUI does not update correctly, the deleted item on the GUI still appears on the screen as you can see below:

Can someone guide me please on how to remove the right row also from the GUI?

EDIT

  1. I update my listview Adapter and use Tags as you answer me, not working.
  2. I also found strange issue when I'm trying to add to Student after remove one. For some reason, it "remember" the last student and return him with full data, as you can see in the update picture.

EDIT2

In case someone want to try it, I add Student class and XML sources:

Student.java

public class Student
{
    public String name;
    public int grade;

    public Student()
    {
        name = "";
        grade = 0;
    }

    public Student(String _name, int _grade)
    {
        name = _name;
        grade = _grade;
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/listView"
        android:layout_gravity="center_horizontal"
        android:dividerHeight="2dp" />
</LinearLayout>

listview_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c4e0ff">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Spinner
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/spNames" />

        <Spinner
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:id="@+id/spGrades"
            android:layout_marginLeft="10dp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Set Value"
            android:id="@+id/btn_setValue" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Medium Text"
            android:id="@+id/tv_Value" />

        <Button
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Remove"
            android:id="@+id/btn_Remove"
            android:layout_marginLeft="30dp" />

    </LinearLayout>

</LinearLayout>

回答1:


The problem is with

holder.spNames.setTag(position); 

and

holder.spNames.setSelection((Integer) holder.spNames.getTag());

As you are setting the tag for name in "onItemSelected()". When you delete the item you remove the item from the list of student, but what about the tag.

Let us say you deleted the item at 0.

Now, when "notifyDataSetChanged()" is called it will repopulate the listview based on the data is available. Now , here

else{
        holder = (ViewHolder) view.getTag();
    }

is getting called. When the i= 0 in getView() you will get the view of the "0 th" index of the previous populated list. Hence, the "(Integer) holder.spNames.getTag()" will point to the previous tag (i.e. 0 of the previous list) . This might be the cause of the issue.

I am posting updated code of

listviewAdapter

public class ListviewAdapter extends BaseAdapter
{
public Activity context;
public LayoutInflater inflater;
private ArrayList<Student> studentID;
private String[] studentsNames;
private boolean isDeleted;
public ListviewAdapter(Activity context, ArrayList<Student> students, String[] names)
{
    super();
    studentID = students;
    studentsNames = names;

    this.context = context;
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public int getCount() {
    return studentID.size();
}

@Override
public Student getItem(int position) {
    return studentID.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}




public class ViewHolder {
    Spinner spNames, spGrades;
    TextView tvValue;
    Button btnSet, btnRemove;
    int index;
}

@Override
public View getView(int i, View view, final ViewGroup viewGroup)
{
    final ViewHolder holder;
    if (view == null) {
        holder = new ViewHolder();
        view = inflater.inflate(R.layout.listview_row, null);

        holder.spNames = (Spinner) view.findViewById(R.id.spNames);
        holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
        holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
        holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
        holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
        Log.e("IAM", "CALLED");
        view.setTag(holder);
        //holder.spNames.setTag(0);
        //holder.spGrades.setTag(0);
    }
    else{
        holder = (ViewHolder) view.getTag();
    }
    holder.index=i;
   if(isDeleted){
       holder.tvValue.setText(getItem(holder.index).getName()+ " got " + getItem(holder.index).getGrade()); 
            }
    // pop spinner names
    ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<String>
            (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
    studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spNames.setAdapter(studentsNamesAdapater);

    // pop spinner grades
    String[] grades = new String[101];
    for (int grade = 0; grade < 101; grade++)
        grades[grade] = String.valueOf(grade);

    final ArrayAdapter<String> studentsGradesAdapter = new ArrayAdapter<String>
            (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
    studentsGradesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spGrades.setAdapter(studentsGradesAdapter);


    // select the right spNames index
    //holder.spNames.setSelection((Integer) holder.spNames.getTag());
    holder.spNames.setSelection(getItem(holder.index).getNameIndex());
    // saving spinner index
    holder.spNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            //holder.spNames.setTag(position);
            getItem(holder.index).setNameIndex(position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });

    // select the right spGrades index
   // holder.spGrades.setSelection((Integer) holder.spGrades.getTag());

    holder.spGrades.setSelection(getItem(holder.index).getGrageIndex());
    // saving spinner index
    holder.spGrades.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
           // holder.spGrades.setTag(position);
            getItem(holder.index).setGrageIndex(position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });

    // set (variable and textview)
    holder.btnSet.setTag(i);
    holder.btnSet.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // update studentID
            //final int position = getRowPosition(v);
            int position = (Integer) v.getTag();
           // Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
           // studentID.set(position, tmp);
            getItem(position).setName(holder.spNames.getSelectedItem().toString());
            getItem(position).setGrade(Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
            holder.tvValue.setText(getItem(position).getName()+ " got " + getItem(position).getGrade());
            //((MainActivity) context).updateStatus(position);
        }
    });


    // remove row
    holder.btnRemove.setTag(i);
    holder.btnRemove.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //final int position = getRowPosition(v);
            int position = (Integer) v.getTag();
            studentID.remove(position);
            notifyDataSetChanged();
            //((MainActivity) context).adapter.notifyDataSetChanged();
            isDeleted=true;
            // for debug
            String dStatus = "Vector size: " + studentID.size() + "\n";
            for (int index = 0; index < studentID.size(); index++)
                dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
            Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
        }
    });

    return view;
}

public void printS() {
    for (int i = 0; i <studentID.size(); i++) {
        Log.e("NAME", ""+studentID.get(i).getName());
        Log.e("GRADE", ""+studentID.get(i).getGrade());
    }
}
}

and

Student

public class Student {
public String name;
public int grade;
private int nameIndex;
private int grageIndex;

public Student()
{
    name = "";
    grade = 0;
}

public Student(String _name, int _grade)
{
    name = _name;
    grade = _grade;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getGrade() {
    return grade;
}

public void setGrade(int grade) {
    this.grade = grade;
}

public int getNameIndex() {
    return nameIndex;
}

public void setNameIndex(int nameIndex) {
    this.nameIndex = nameIndex;
}

public int getGrageIndex() {
    return grageIndex;
}

public void setGrageIndex(int grageIndex) {
    this.grageIndex = grageIndex;
}
}



回答2:


I suggest that you use setOnItemClickListener method on your list view. There are some advantages :

  • You won't have to create a new OnClickListener each time your item is rendered
  • You have a direct access to the position of the view and to the view itself : onItemClick(AdapterView<?> parent, View view, int position, long id)

You can then use that position to remove you item from your adapter.




回答3:


Basically this problem happens because of View recycling. Add these two methods to your adapter class

 @Override
 public int getViewTypeCount() {
     return studentID.size() + 1;
 }

 @Override
 public int getItemViewType(int position) {
     return position;
 }

Here is link which explains why you have to add these two methods. Getting an issue while checking the dynamically generated checkbox through list view

I hope it helps!




回答4:


you need to set tag as position for button each time you create, then inside onclick, get the tag, it will return you the correct position

inside your getView

holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
holder.btnRemove.setTag(position);

and then onClick(View view)

int position = (Integer) ((Button) view).getTag();
//remove item from position, and do the stuff here

then try to remove the item from position.

alternate solution:




回答5:


simply get the position of the item to be deleted on click and then remove it from your arraylist using arraylist.remove(position) and then call notifyDataSetChanged.




回答6:


You are doing it wroing, look at below codes.

@Override
public View getView(int i, View view, final ViewGroup viewGroup)
{
     if (view == null){
         view == LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.xxx, null);
         ViewHolder holder = new ViewHolder();
         holder.spNames = (Spinner) view.findViewById(R.id.spNames);
         holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
         holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
         holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
         holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
         view.setTag(holder);

     }else{
         holde = view.getTag();
     }

     /** You have to redefine each view every time because it can be recycled by the listview **/
     ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
        studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spNames.setAdapter(studentsNamesAdapater);
     /** so no **//


     /** you can set the position to the button as a tag **/
     holder.btnRemove.setTag(i);

     /** you MUST set the button OnClickListener after the view holder is created. you MUST NOT set the listener inside the if (view==null) pattern. **/
    holder.btnRemove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                int position = (Integer)v.getTag(); //Get the position which you would like you remove from the button.
                studentID.remove(position);
                notifyDataSetChanged();


            }
        });
}

You may want to know why you have to dealing ListViews like this, I can't tell you why at this point, but you must HAVE to HAVE a full understanding of how AdapterViews(like ListView, GridView, RecyclerView) work if you want to be a brilliant mobile (Android, iOS and other mobile device are the same way) developer.

Look at this like: http://developer.android.com/guide/topics/ui/layout/listview.html

Welcome to the coding world, you still have a long way to run. Good luck.




回答7:


Change:

    @Override
        public View getView(int final position, View view, final ViewGroup viewGroup)
        {
            final ViewHolder holder;
            if (view == null) {
                holder = new ViewHolder();
                view = inflater.inflate(R.layout.listview_row, null);

                holder.spNames = (Spinner) view.findViewById(R.id.spNames);
                holder.spGrades = (Spinner) view.findViewById(R.id.spGrades);
                holder.tvValue = (TextView) view.findViewById(R.id.tv_Value);
                holder.btnSet = (Button) view.findViewById(R.id.btn_setValue);
                holder.btnRemove = (Button) view.findViewById(R.id.btn_Remove);
               view.setTag(viewHolder);

            }
            else{
                holder = (ViewHolder) view.getTag();
            }

    // pop spinner names
                ArrayAdapter<String> studentsNamesAdapater = new ArrayAdapter<>
                        (view.getContext(), android.R.layout.simple_spinner_dropdown_item, studentsNames);
                studentsNamesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                holder.spNames.setAdapter(studentsNamesAdapater);

                // pop spinner grades
                String[] grades = new String [101];
                for (int grade = 0; grade < 101; grade++)
                    grades[grade] = String.valueOf(grade);

                final ArrayAdapter<String> studentsGradesAdapater = new ArrayAdapter<>
                        (view.getContext(), android.R.layout.simple_spinner_dropdown_item, grades);
                studentsGradesAdapater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                holder.spGrades.setAdapter(studentsGradesAdapater);

                // set (variable and textview)
                holder.btnSet.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // update studentID
                        Student tmp = new Student(holder.spNames.getSelectedItem().toString(), Integer.valueOf(holder.spGrades.getSelectedItem().toString()));
                        studentID.set(position, tmp);
                        ((MainActivity) context).updateStatus(position);
                    }
                });

                // remove row
                holder.btnRemove.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v)
                    {
                        studentID.remove(position);
                        notifyDataSetChanged();

                        // for debug
                        String dStatus = "Vector size: " + studentID.size() + "\n";
                        for (int index = 0; index < studentID.size(); index++)
                            dStatus += studentID.get(index).name + " " + studentID.get(index).grade + "\n";
                        Toast.makeText(v.getContext(), dStatus, Toast.LENGTH_SHORT).show();
                    }
                });

            return view;
        }


来源:https://stackoverflow.com/questions/32045631/wrong-row-deleted-from-custom-listview-with-spinner

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!