简体   繁体   中英

Checkbox unchecked automatically while scrolling listview

I have a listview with checkbox and textbox .When I check the checkbox and scroll it up and down then it is automatically uncheck. How to solve this problem in listview? please give me solution.
below i put my screen shot and code.

screen shot

activity_main.xml

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

    <ListView
        android:id="@+id/lvMain"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

        <Button
            android:id="@+id/btnSelectAll"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:text="Select All" >
        </Button>
    </LinearLayout>

list_item.xml

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

    <CheckBox
        android:id="@+id/cbItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:buttonTint="#000000">
    </CheckBox>

    <TextView
        android:id="@+id/tvItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000">
    </TextView>

</LinearLayout>

MainActivity.java

package com.example.checkallcheckbox;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;

public class MainActivity extends Activity {
    ListView lvMain;
    String[] name = { "Jenis", "Pratik", "Jaydeep", "Hiren", "Himansu",
            "yagnik", "Atul", "Prakas", "Nihal", "Darshan", "Chetan", "Sagar",
            "Nikhil", "Sanket", "Rahul", "Jigar" };

    Button btnSelectAll;
    boolean isSelectAll = true;
    ListAdapter adapter;

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

        lvMain = (ListView) findViewById(R.id.lvMain);
        btnSelectAll = (Button) findViewById(R.id.btnSelectAll);

        btnSelectAll.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                if(isSelectAll)
                {
                adapter = new ListAdapter(getApplicationContext(), name, true);
                lvMain.setAdapter(adapter);
                adapter.notifyDataSetChanged();
                btnSelectAll.setText("ClearAll");
                isSelectAll=false;
                }
                else
                {
                    adapter = new ListAdapter(getApplicationContext(), name, false);
                    lvMain.setAdapter(adapter);
                    adapter.notifyDataSetChanged();
                    btnSelectAll.setText("CheckAll");
                    isSelectAll=true;

                }

            }
        });

        adapter = new ListAdapter(getApplicationContext(), name,false);
        lvMain.setAdapter(adapter);
        adapter.notifyDataSetChanged();

    }
}

ListAdapter.java

package com.example.checkallcheckbox;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.TextView;

public class ListAdapter extends BaseAdapter {

    Context mContext;
    String[] name;
    private static LayoutInflater inflater = null;
    Boolean state=false;

    public ListAdapter(Context c, String[] name, boolean isSelectAll) {
        // TODO Auto-generated constructor stub
        mContext = c;
        this.name = name;
        inflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        state=isSelectAll;
    }

    @Override
    public int getCount() {

        return name.length;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    public class ViewHolder {
        TextView tvName;
        CheckBox cbName;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup arg2) {
        // TODO Auto-generated method stub
        ViewHolder viewHolder;

        if (convertView == null) {

            convertView = inflater.inflate(R.layout.list_item, null);
            viewHolder = new ViewHolder();
            viewHolder.tvName = (TextView) convertView
                    .findViewById(R.id.tvItem);
            viewHolder.cbName = (CheckBox) convertView
                    .findViewById(R.id.cbItem);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        viewHolder.tvName.setText(name[position]);

        if(state){
            viewHolder.cbName.setChecked(true);
        }else{
            viewHolder.cbName.setChecked(false);
        }

        return convertView;
    }

}

The problem is because your ListView Views are being remade. When you scroll them offscreen, android doesn't hold onto them (the list might be REALLY long so that would mean a lot of views hanging around that you can't see).

So every time you scroll an item back on-screen, Android calls getView again and you return a view without the checkbox checked.

What you need to do is so set a clicked/checked listener on each list item that will be able to remember when they have been checked. Then when android asks you for a view, you can check if it should be checked or not.

You can see that the getItem method is already there to help you with this. You could try overriding ArrayAdapter as that will let you store some simple objects that can hold your 'clicked' state. See: http://developer.android.com/reference/android/widget/ArrayAdapter.html

This is why Aditya asked you where you model class is. Currently you just have a list of students, what you want is a list of students AND whether they have been selected. Maybe a class like this:

class Student
{
    private String mName;
    public Boolean selected;

    public Student(String name)
    {
        mName = name;
    }

    public String getName()
    {
        return mName;
    }
}

Then you could have an array of these instead. When an item is clicked, you set selected on the relevant student item.

Your getView might look like this:

@Override
public View getView(int position, View convertView, ViewGroup arg2) {
    // TODO Auto-generated method stub
    ViewHolder viewHolder;

    if (convertView == null) {

        convertView = inflater.inflate(R.layout.list_item, null);
        viewHolder = new ViewHolder();
        viewHolder.tvName = (TextView) convertView
                .findViewById(R.id.tvItem);
        viewHolder.cbName = (CheckBox) convertView
                .findViewById(R.id.cbItem);
        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

    Student thisStudent = students[position];
    viewHolder.tvName.setText(thisStudent.getName());

    if(state || thisStudent.selected){
        viewHolder.cbName.setChecked(true);
    }else{
        viewHolder.cbName.setChecked(false);
    }

    return convertView;
}

There are lots of guides around about this, here is one such guide that explains how view recycling works in a list (your problem) and also how you can use ArrayAdapter https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView

If you are using a listView , your adapter should implement OnCheckedChangeListener in order to save changes in the checkbox state. In the method that you have to implement onCheckedChanged() (write it inside getView()) you have to get from SharedPreferences the state of that checkbox and update it.

If you look for this approach you will find some example, here is one

  1. Declare a boolean array for holding the checked state of each list item.

  2. Initialze (using for loop) this array in constructor (all assign false).

  3. Record the changes inside the setOnCheckedChangeListener() .

  4. Call setChecked() after setOnCheckedChangeListener() .

Try to use with RecycleView,

public class CardViewActivity extends ActionBarActivity {

 private Toolbar toolbar;

 private RecyclerView mRecyclerView;
 private RecyclerView.Adapter mAdapter;
 private RecyclerView.LayoutManager mLayoutManager;

 private List<Student> studentList;

 private Button btnSelection;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  toolbar = (Toolbar) findViewById(R.id.toolbar);
  btnSelection = (Button) findViewById(R.id.btnShow);

  studentList = new ArrayList<Student>();

  for (int i = 1; i <= 15; i++) {
   Student st = new Student("Student " + i, "androidstudent" + i
     + "@gmail.com", false);

   studentList.add(st);
  }

  if (toolbar != null) {
   setSupportActionBar(toolbar);
   getSupportActionBar().setTitle("Android Students");

  }

  mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

  // use this setting to improve performance if you know that changes
  // in content do not change the layout size of the RecyclerView
  mRecyclerView.setHasFixedSize(true);

  // use a linear layout manager
  mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

  // create an Object for Adapter
  mAdapter = new CardViewDataAdapter(studentList);

  // set the adapter object to the Recyclerview
  mRecyclerView.setAdapter(mAdapter);

  btnSelection.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    String data = "";
    List<Student> stList = ((CardViewDataAdapter) mAdapter)
      .getStudentist();

    for (int i = 0; i < stList.size(); i++) {
     Student singleStudent = stList.get(i);
     if (singleStudent.isSelected() == true) {

      data = data + "\n" + singleStudent.getName().toString();
      /*
       * Toast.makeText( CardViewActivity.this, " " +
       * singleStudent.getName() + " " +
       * singleStudent.getEmailId() + " " +
       * singleStudent.isSelected(),
       * Toast.LENGTH_SHORT).show();
       */
     }

    }

    Toast.makeText(CardViewActivity.this,
      "Selected Students: \n" + data, Toast.LENGTH_LONG)
      .show();
   }
  });

 }

For more see this

For ListView see this

I was able to resolve mine and this might help you too.

I had the same issue of auto-selection and dis-selection, when scrolling through an ArrayList of checkboxes binded on a RecyclerView.

The issue was because, in my RecyclerView adapter, I set an OnCheckedChange listener on each of the checkbox items in the ArrayList while binding data to the views, which made the checkboxes to auto-select and dis-select itself when scrolled.

See my initial code below;

holder.subCategoriesCheckBox.setOnCheckedChangeListener((compoundButton, b) -> {
        if (holder.subCategoriesCheckBox.isChecked()){
            holder.subCategoriesCheckBox.setChecked(false);
            subCategory.setSelected(false);
        }
        else {
            holder.subCategoriesCheckBox.setChecked(true);
            subCategory.setSelected(true);
        }
    });

The solution was to set an OnClickListener on the checkboxes instead of OncheckedChange listener as below;

holder.subCategoriesCheckBox.setOnClickListener(view -> {
        if (holder.subCategoriesCheckBox.isSelected()){
            holder.subCategoriesCheckBox.setSelected(false);
            subCategory.setSelected(false);
        }
        else {
            holder.subCategoriesCheckBox.setSelected(true);
            subCategory.setSelected(true);
        }
    });

Hope this gives you an idea on how to go about yours

You should use a recyclerView and control the check states in a list, you can add an isDefault boolean is well that will get updated when there is a change in a different row. I have a tutorial for this here

holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
            if (item.isChecked != isChecked) {
                setCheckboxTextColor(isChecked, holder)
                item.isChecked = isChecked

            when (item.rowType) {
                RowType.TopHeader -> {
                    val indexList = mutableListOf<Int>()
                    productList.filter { it.rowType != RowType.TopHeader }.forEach {
                        it.isChecked = isChecked
                        indexList.add(productList.indexOf(it))
                    }
                    indexList.forEach {
                        notifyItemChanged(it)
                    }
                }
                RowType.CatHeader -> {
                    val indexList = mutableListOf<Int>()
                    productList.filter { it.rowType == RowType.ProductRow && it.category == item.category }
                            .forEach {
                                it.isChecked = isChecked
                                indexList.add(productList.indexOf(it))
                            }
                    indexList.forEach {
                        notifyItemChanged(it)
                    }
                    isAllItemsSameStatus() //for header

                }
                RowType.ProductRow -> {
                    isAllItemsSameStatus(item.category) //set prep area accordingly
                    isAllItemsSameStatus() //set top header
                }
            }
        }
    }
}

private fun setCheckboxTextColor(isChecked: Boolean, holder: TableViewHolder) {
    if (isChecked) {
        holder.checkBox.setTextColor(context.getColor(R.color.black))
    } else {
        holder.checkBox.setTextColor(context.getColor(R.color.grey))
    }
}

private fun isAllItemsSameStatus(cat: String? = null) {
   val row : RowModel
   var isChecked: Boolean = true
   var position: Int = 0

    if(cat != null){
    val catRow = productList.find { it.rowType == RowType.CatHeader && it.category == cat }
        catRow?.let {
        val subList = productList.filter { it.category == it.category && it.rowType == RowType.ProductRow }
        isChecked = subList.filter { it.isChecked }.size == subList.size
        position = productList.indexOf(catRow)
    }
        if(catRow == null)
            return
        else
            row = catRow
    }
    else{
        row = productList[0]
        isChecked = productList.filter { it.rowType != RowType.TopHeader && it.isChecked }.size == productList.size - 1
        position = 0
    }

    updateHeader(row, isChecked, position)

}


private fun updateHeader(item: RowModel, isChecked: Boolean, position: Int) {
    if (item.isChecked != isChecked) // no need to update if no change
    {
        item.isChecked = isChecked
        notifyItemChanged(position)

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM