How to keep the states and positions of CheckedTextView in list view?

355 Views Asked by At

I have a listview populated with checkedtextview using custom adapter,the problem that I'm having is that the when I for example check one check boxe another check box in the list view is checked, now I fairly understand the concept of list view and how views are recycled, so what is that I'm doing wrong here? and another issue that I'm having, is that the position of the check boxes keeps changing when scrolling
I have tries multiple solutions and keep on trying, and any help to understand this issue is surely appreciated

this the checkedtextview xml

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

    <CheckedTextView
        android:id="@+id/checkedTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:checked="false"
        android:drawableLeft="?android:attr/listChoiceIndicatorMultiple"
        android:gravity="center"
        android:paddingBottom="@dimen/virtical_padding"
        android:textAppearance="?android:textAppearanceLarge"
        tools:text="Service 1" />

    <Button
        android:id="@+id/plus_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/check_box_list_margin"
        android:layout_marginLeft="@dimen/check_box_list_margin"
        android:focusable="false"
        android:text="@string/plus"
        android:textColor="#000"
        android:visibility="invisible" />

    <TextView
        android:id="@+id/quantity_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:text="@string/quantity_default"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textColor="#000"
        android:visibility="invisible" />

    <Button
        android:id="@+id/minus_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false"
        android:text="@string/minus"
        android:textColor="#000"
        android:visibility="invisible" />

</LinearLayout>

this is the code for the OnItemClickListener

 mServiceListView.setOnItemClickListener( new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                //find the checkedTextView inside the relative view
                  ctv = (CheckedTextView) view.findViewById( R.id.checkedTextView );
                  mServiceQuantity = (TextView) view.findViewById( R.id.quantity_tv );
                  mMinusBtn = ( Button) view.findViewById( R.id.minus_btn );
                  mPlusBtn = (Button) view.findViewById( R.id.plus_btn );
                ((CheckedTextView) ctv).toggle();
                Service service = (Service) parent.getItemAtPosition(position);

                if (ctv.isChecked()) {
                    selectedServices.add(service.getServiceNum());
                    //make the quantity option visible
                    mMinusBtn.setVisibility( View.VISIBLE );
                    mPlusBtn.setVisibility( View.VISIBLE );
                    mServiceQuantity.setVisibility( View.VISIBLE );

                } else {
                    //remove the service and quantity from table
                    selectedServices.remove( service.getServiceNum() );

                    selectedServicesHash.remove( service.getServiceNum() );
                    //the quantity option will be invisible
                    mMinusBtn.setVisibility( View.INVISIBLE );
                    mPlusBtn.setVisibility( View.INVISIBLE );
                    mServiceQuantity.setVisibility( View.INVISIBLE );

                }
            }
        } );

and this is the code for the custom adapter

package com.example.android.bookkeepingapp;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;


public class ServiceAdapterCheckBox extends ArrayAdapter<Service> {

    private Hashtable<String, Integer> selectedServicesHash;
    private  Set<String> serviceNums;

    public ServiceAdapterCheckBox(@NonNull Context context, int resource, List<Service> objects
            , Hashtable<String, Integer> selectedServiceHash) {
        super( context, resource, objects );
        this.selectedServicesHash = selectedServiceHash;
    }

    public void setSelectedServicesHash(Hashtable<String, Integer> selectedServices) {
        this.selectedServicesHash = selectedServices;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        //Find the current object
            Service service = getItem(position);
          //put all the services name of the hashtable in a list
          serviceNums= selectedServicesHash.keySet();
            final ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.service_item_check_box,
                    parent, false);

            holder.textCheckBox = (CheckedTextView) convertView.findViewById(R.id.checkedTextView);
            holder.minBtn = (Button) convertView.findViewById( R.id.minus_btn );
            holder.plusBtn = (Button) convertView.findViewById( R.id.plus_btn );
            holder.quantityTv = (TextView) convertView.findViewById( R.id.quantity_tv );
            convertView.setTag(holder);
        }
        else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        //supply the textViews with the correct data
        assert service != null;
        holder.textCheckBox.setText( service.getServiceName() );
        //set the quantity value set by the user

        holder.plusBtn.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //holder.plusBtn.setFocusable( true );
                int quantityValue = Integer.parseInt(holder.quantityTv.getText().toString());
                quantityValue++;
                holder.quantityTv.setText( String.valueOf( quantityValue ) );


            }
        } );

        holder.minBtn.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //holder.plusBtn.setFocusable( true );
                int quantityValue = Integer.parseInt(holder.quantityTv.getText().toString());
                if(quantityValue == 1){
                    quantityValue = 1;
                }
                else {
                    quantityValue--;
                }
                holder.quantityTv.setText( String.valueOf( quantityValue ) );


            }
        } );

        //if the there are services in the hash table
        if(serviceNums.size() != 0) {
            Log.v(this.getContext().toString(), "there are services selected");
            //put all the services name of the hashtable in a list
            for (String s : serviceNums) {
                if (service.getServiceNum().equals( s )) {
                    //set the check text with the current position to true
                    //holder.textCheckBox.setChecked( true );
                    //set the quantity text box to the correct value set by the user
                    //holder.quantityTv.setText(selectedServicesHash.get(s));
                    //set the quantity selectors to visible
                    holder.plusBtn.setVisibility( View.VISIBLE );
                    holder.minBtn.setVisibility( View.VISIBLE );
                    holder.quantityTv.setVisibility( View.VISIBLE );

                }
            }
        }

        else{
            Log.v(this.getContext().toString(), "no the list is empty");
            //set the quantity text box to the correct value set by the user
            holder.quantityTv.setText("1");
        }


        return  convertView;
    }

    public Hashtable<String, Integer> getSelectedServicesHash() {
        return selectedServicesHash;
    }

    /**
     * customizable toast
     *
     * @param message
     */
    private void toastMessage(String message) {
        Toast.makeText( getContext(), message, Toast.LENGTH_SHORT ).show();
    }

    private class ViewHolder {

        protected CheckedTextView textCheckBox;
        protected  Button plusBtn;
        protected  Button minBtn;
        protected TextView quantityTv;

    }
}

all I want is to get the correct checked box with the correct position Thanks...

1

There are 1 best solutions below

0
dominicoder On

Because views are recycled in a ListView, you have to update the view state to what it should be in your adapter code. Otherwise the views will just have the checked state of an old view that is being reused.

So along with updating the text on the view, you need to set its checked state.

//supply the textViews with the correct data
assert service != null;
holder.textCheckBox.setText( service.getServiceName() );

// ALSO UPDATE THE CHECKED STATE - FROM YOUR POSTED CODE, I ASSUME IT SHOULD BE
// CHECKED IF IT APPEARS IN THE "selectedServices" list
holder.textCheckBox.setChecked(selectedServices.contains(service));