Получение проблемы при проверке динамически созданного флажка через просмотр списка

Я знаю, что этот вопрос уже задан другими членами, и решение также дается некоторыми членами, но дело в том, что я не нашел решения, подходящего для моего приложения. Я создаю приложение, в котором у меня есть экран, на котором будет отображаться динамическое listview с элементами списка, а также три текстовых поля (один для имени кандидата, а два других - для clockIn и clockOut, которые будут отображаться после выбора даты и времени на date time picker). Теперь моя проблема в том, что когда я проверяю первый флажок (у меня есть 15 имен кандидатов с флажками), автоматически проверяется 10-й флажок, и это также происходит со 2-м и 11-м, 3-м и 12-м и т.д. (наоборот, true). Я предоставляю свой класс адаптера и элемент списка xml.

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;

import com.android.feedback.ListViewCheckBox;

public class DemoAdapter extends ArrayAdapter<String>{

    private final List<String> list;
    private final Activity context;
    LayoutInflater inflater;
    TextView CItv,COtv;
    static ViewHolder holder;
    View view;

    public DemoAdapter(Activity context, List<String> list) {
        super(context, R.layout.test_listitems,list);
        // TODO Auto-generated constructor stub

        this.context = context;
        this.list = list;
    }

    static class ViewHolder {
        protected TextView text,CItv,COtv;
        protected CheckBox checkbox;
    }


    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
          view = null;
        //  final ArrayList<Integer> checkedItems = new ArrayList<Integer>(); 
        if (convertView == null) {

            inflater = context.getLayoutInflater();
            view = inflater.inflate(R.layout.test_listitems, null);
            final ViewHolder viewHolder = new ViewHolder();
            viewHolder.CItv = (TextView)view.findViewById(R.id.CITextView);
            viewHolder.COtv = (TextView)view.findViewById(R.id.COTextView);
            viewHolder.text = (TextView) view.findViewById(R.id.empTextView);
            viewHolder.checkbox = (CheckBox) view.findViewById(R.id.empCheckBox);

            viewHolder.checkbox
                    .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView,
                                boolean isChecked) {


                            if(isChecked){  
                                Object o = getItemId(position+1);
                                String keyword = o.toString();
                                Toast.makeText(getContext(), "You selected: " + keyword, 2000).show();

                            Toast.makeText(getContext(),ListViewCheckBox.DT_selected, 2000).show();
                                //  holder.CItv.setText(ListViewCheckBox.DT_selected);
                                //  holder.COtv.setText(ListViewCheckBox.outDT_selected);
                                }

                            else{
                                Object o = getItemId(position+1);
                                String keyword = o.toString();
                                //Toast.makeText(getContext(), "You unselected: " + keyword, 2000).show();
                                holder.CItv.refreshDrawableState();
                                holder.COtv.refreshDrawableState();

                            }



                        }
                    });

            view.setTag(viewHolder);
            viewHolder.checkbox.setTag(list.get(position));
            viewHolder.checkbox.setId(position);
        } else {
            view = convertView;
            ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
        }
        holder = (ViewHolder) view.getTag();
        holder.text.setText(list.get(position));



        return view;

        }

    }

и XML.

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


    <TableLayout android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:stretchColumns="1,2,3">

    <TableRow >


     <CheckBox    android:text=" " android:id="@+id/empCheckBox"
                  style="@style/Check" android:textColor="#000000"
                  android:textSize="12dp" 
                  android:layout_weight="1"/>

     <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/empTextView"
                 style="@style/CICOTextView"
                 android:layout_weight="2"/>

    <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/CITextView"
                 style="@style/CICOTextView"
                 android:text=""
                 android:layout_weight="3"/>    

    <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/COTextView"
                 style="@style/CICOTextView"
                 android:text=""
                 android:layout_weight="4"/>     

    </TableRow>
    </TableLayout>
</LinearLayout>

Пожалуйста, помогите мне избавиться от проблемы. (ListViewCheckBox - это класс, который генерирует список и сохраняет значение даты и времени в переменных DT_selected и outDT_selected).

Ответы

Ответ 1

Я отредактировал свой ответ, поэтому общая информация находится вверху. Вы найдете фактический ответ на этот вопрос внизу...


Вот настоящая идея и процесс утилизации, чтобы вы могли понять, что не так с вашей реализацией и идеей getView (а может быть, и с другими, когда они найдут этот вопрос и ответ). Ниже приведен пример кода, просто игнорируйте часть типа, так как это дополнительная информация.

  • Этап 1: Создание элемента для утилизации (convertView - null):
    Это означает, что вы создаете макет и общее состояние, общее для всех элементов. Если у вас есть слушатели, вы добавили их сюда и разработали их таким образом, чтобы позже они могли реагировать на изменения позиции (когда они повторно используются). Так, например, задав позицию как тег на соответствующем представлении, чтобы слушатель мог поймать эту информацию и узнать, на каком элементе она работает. Вы не можете использовать представления для хранения данных. Поэтому, когда слушатель изменяет состояние элемента списка, вы должны сохранять эти данные (в массиве данных, в базе данных SQLite и т.д.) И использовать его в фазе 2.

  • Этап 2: состояние элемента настройки для данной позиции:
    Вы устанавливаете визуальное состояние для элемента. Здесь необходимо установить все, что может измениться индивидуально для элемента (текст, состояние флажка, цвета и т.д.). Не только то, что изменилось для текущего элемента, но могло быть изменено другим элементом. Таким образом, вы убедитесь, что представление не используется в недопустимом состоянии, поскольку оно повторно используется из другого элемента списка раньше.


Активный ответ был удален/отредактирован, но предлагал реализовать getItemViewType и getViewTypeCount, чтобы каждый элемент списка имел свой собственный тип вида. Отредактированный ответ показывает, как решить проблему так, как она описана здесь.

Повторное выполнение getItemViewType и getViewTypeCount работает, но вы, очевидно, неправильно интерпретируете его (сравните мой пример далее ниже и/или этот ответ).

Эти два метода используются для использования двух (или более) элементов списка, которые полностью отличаются друг от друга (например, общий элемент списка и разделитель, который содержит только заголовок), а не во избежание повторного использования вида, которое может быть повторно использовано.

Если вы все равно используете их для решения своей проблемы, вы, вероятно, не поняли, о чем я уже говорил. Так, например, у вас есть 1000 элементов, и вы делаете взломанный тип вида, тогда вы создаете 1000 просмотров (иерархии), а, вероятно, 10, которые могут быть легко использованы повторно. Это не имеет значения, если у вас есть только 20 предметов или около того, но если вы используете эту технику для больших списков, вы просто теряете (драгоценную) память!

Вот пример:

void getItemViewType(int position) {
    return isItemAtPositionSeperator(position) ? 1 : /* normal item */ 0;
}

void int getViewTypeCount() {
    return 2; // normal item and separator
}

void View getView(int position, View convertView, ViewGroup parent) {
    int type = getItemViewType(position);

    // phase 1: see my explanation 
    if (convertView == null) {
        if (type == 0) {
            // setup your common item view - inflate it and set to convertView
        } else {
            // setup separator view - inflate it and set to convertView
        }
    }

    // phase 2: see my explanation 
    if (type == 0) {
        // set the state of the common item view based on the position
        // rely on the fact that convertView contains the view hierarchy
        // you created in convertView == null && type == 0
    } else {
        // set state of the separator based on the position
        // rely on the fact that convertView contains the view hierarchy
        // you created in convertView == null && type != 0 (else part)
    }

    return convertView;
}

Фактический ответ на вопрос...

Я знаю, в чем проблема, но не могу придумать элегантное решение прямо сейчас...

Ваша проблема заключается в том, что вы устанавливаете прослушиватель кликов один раз с помощью viewHolder.checkbox.setOnCheckedChangeListener при создании представления. Поэтому он перерабатывается/повторно используется для элементов, когда вы прокручиваете, а поведение клика относится к неправильному элементу списка.

Не пытайтесь жестко закодировать позицию, используя внешний final position. Попробуйте установить viewHolder.checkbox.setTag(position) до return, а затем используйте (Integer) buttonView.getTag() вместо position+1. Таким образом, ваш переработанный вид сохранит фактическое положение.

Когда вы нажимаете флажок, вы должны сохранять состояние в другом месте. Не полагайтесь на состояние пользовательского интерфейса для этого (потому что оно будет переработано). Поэтому вызовите viewHolder.checkbox.setChecked(persistedState) до return.

Я надеюсь, что это имеет смысл, и вы получаете идею...; -)

Ответ 2

Попробуйте это,

Создайте класс POJO, который будет поддерживать состояние выбранных элементов флажка, например,

public class Model {

    private String name;
    private boolean selected;

    public Model(String name) {
        this.name = name;
        selected = false;
    }

    public String getName() {
        return name;
    }

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

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

И это то, что вам нужно применить к методу getView() в адаптере.

public View getView(int position, View convertView, ViewGroup parent) {

        checkBoxCounter = 0;      
        checkBoxInitialized = 0;   
        if (convertView == null) {
            final ViewHolder viewHolder = new ViewHolder();
            LayoutInflater inflator = context.getLayoutInflater();
            convertView = inflator.inflate(R.layout.main, null);
            viewHolder.text = (TextView) convertView.findViewById(R.id.label);
            viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.check);

            viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    Model element = (Model) viewHolder.checkbox.getTag();
                    element.setSelected(buttonView.isChecked());

                    if(checkBoxCounter <= checkBoxInitialized){
                    // increment counter, when we scroll the List it execute onCheckedChanged everytime so by using this stuff we can maintain the state
                    checkBoxCounter++;
                }
                else{
                    Model element = (Model) viewHolder.checkbox.getTag();
                    element.setSelected(buttonView.isChecked());

                    if(element.isSelected())
                    Toast.makeText(getContext(), "You selected "+ element.getName(), Toast.LENGTH_LONG).show();
                    else
                        Toast.makeText(getContext(), "Not selected "+ element.getName(), Toast.LENGTH_LONG).show();
                    }
                }
            });
            convertView.setTag(viewHolder);
            viewHolder.checkbox.setTag(list.get(position));
        } 
        else{
            ((ViewHolder) convertView.getTag()).checkbox.setTag(list.get(position));
        }

        ViewHolder viewHolder = (ViewHolder) convertView.getTag();
        viewHolder.text.setText(list.get(position).getName());
        viewHolder.checkbox.setChecked(list.get(position).isSelected());
        return convertView;
    }

Для дальнейшего изучения того, как это работает, вы можете посмотреть полный пример. А также вы можете взглянуть на Как работает ListView

ОБНОВЛЕНИЕ: Недавно я добавил решение для этого типа сообщений в блоге. ListView с проблемой прокрутки CheckBox

Ответ 4

Вы должны использовать логический массив, чтобы отслеживать проверенное состояние каждого элемента списка, записывать изменения внутри setOnCheckedChangeListener(), а затем вызывать setChecked() после setOnCheckedChangeListener().