Search code examples
androidlistviewandroid-togglebutton

Android ListView with toggle button


I'm having a problem when my ListView has more itens that can appear on the screen; in other words, when it gets scroll. The problem is, when I click in one of the toggle buttons, it's change the visibility of the ImageView. However, when I click on it, it is changing the visibility of more than the respective clicked.

I'm using an adapter to display the list itens.

I added the code below:

public class CriteriosAdapter extends ArrayAdapter<Criterio> {

private Context context;

public CriteriosAdapter(Context context, List<Criterio> objects) {
    super(context, 0, objects);
    this.context = context;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final Criterio criterio = getItem(position);
    final CriterioViewHolder viewHolder;

    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.fragment_row, parent, false);

        viewHolder = new CriterioViewHolder();
        viewHolder.txtCriterio = (TextView)convertView.findViewById(R.id.txtCriterio);
        viewHolder.tgIrregular = (ToggleButton)convertView.findViewById(R.id.tgIrregular);
        viewHolder.btnCam = (ImageView) convertView.findViewById(R.id.btnCam);

        convertView.setTag(viewHolder);
    }
    else {
        viewHolder = (CriterioViewHolder)convertView.getTag();
    }

    viewHolder.txtCriterio.setText(criterio.nome);
    viewHolder.txtCriterio.setTextColor(context.getColor(R.color.white));
    viewHolder.tgIrregular.setChecked(false);

    viewHolder.tgIrregular.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (viewHolder.tgIrregular.isChecked()) {
                viewHolder.btnCam.setVisibility(View.VISIBLE);
            } else {
                viewHolder.btnCam.setVisibility(View.INVISIBLE);
            }
        }
    });

    viewHolder.btnCam.setOnClickListener(new View.OnClickListener() {
        public void onClick(View arg0) {
            FragmentActivity activity = (FragmentActivity)(context);
            android.support.v4.app.FragmentManager fm = activity.getSupportFragmentManager();
            FotosFragment alertDialog = new FotosFragment();
            alertDialog.show(fm, "fragment_alert");
        }
    });

    return convertView;
}

class CriterioViewHolder {

    TextView txtCriterio;
    ToggleButton tgIrregular;

    ImageView btnCam;

}

}

Could someone help me with it?

Thank you.

EDIT

I added everything as you describe. However, I use that adapter in 5 fragments, if I go to another, and come back or scroll the list, it still lost the value.

I used criterio.getHash, because it is unique for all. And position, can repeat in other fragment, and make it weird.

public class CriteriosAdapter extends ArrayAdapter<Criterio> {

private Context context;
public static Map<String, Criterio> irregularidades = new HashMap<String, Criterio>();
HashMap<String, Boolean> toggleButtonStateTracker = new HashMap<>();

public CriteriosAdapter(Context context, List<Criterio> objects) {
    super(context, 0, objects);
    this.context = context;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final Criterio criterio = getItem(position);
    final CriterioViewHolder viewHolder;

    if (!toggleButtonStateTracker.containsKey(criterio.getHash())){
        toggleButtonStateTracker.put(criterio.getHash(),false);
    }

    Log.e("Toggle Track:", toggleButtonStateTracker.toString());

    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.fragment_row, parent, false);

        viewHolder = new CriterioViewHolder();
        viewHolder.txtCriterio = (TextView)convertView.findViewById(R.id.txtCriterio);
        viewHolder.tgIrregular = (ToggleButton)convertView.findViewById(R.id.tgIrregular);
        viewHolder.btnCam = (ImageView) convertView.findViewById(R.id.btnCam);

        convertView.setTag(viewHolder);
    }
    else {
        viewHolder = (CriterioViewHolder)convertView.getTag();
    }

    viewHolder.txtCriterio.setText(criterio.nome);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        viewHolder.txtCriterio.setTextColor(context.getColor(R.color.white));
    } else {
        viewHolder.txtCriterio.setTextColor(context.getResources().getColor(R.color.white));
    }

    viewHolder.tgIrregular.setId(criterio.id);

    final boolean isChecked = toggleButtonStateTracker.get(criterio.getHash());
    viewHolder.tgIrregular.setChecked(isChecked);

    viewHolder.btnCam.setTag(criterio.hash);

    if (isChecked) {
        viewHolder.btnCam.setVisibility(View.VISIBLE);
    } else {
        viewHolder.btnCam.setVisibility(View.INVISIBLE);
    }

    viewHolder.tgIrregular.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            toggleButtonStateTracker.put(criterio.getHash(), isChecked);
            if (isChecked) {
                viewHolder.btnCam.setVisibility(View.VISIBLE);
                irregularidades.put(criterio.getHash(), criterio);
            } else {
                viewHolder.btnCam.setVisibility(View.INVISIBLE);
                irregularidades.remove(criterio.getHash());
            }
        }
    });

    viewHolder.btnCam.setOnClickListener(new View.OnClickListener() {
        public void onClick(View arg0) {
            FragmentActivity activity = (FragmentActivity) (context);
            android.support.v4.app.FragmentManager fm = activity.getSupportFragmentManager();
            FotosFragment alertDialog = new FotosFragment();

            Bundle args = new Bundle();
            args.putString("criterioTag", criterio.getHash());
            alertDialog.setArguments(args);

            alertDialog.setCancelable(false);
            alertDialog.show(fm, "fragment_alert");
        }
    });

    return convertView;
}

class CriterioViewHolder {

    TextView txtCriterio;
    ToggleButton tgIrregular;
    ImageView btnCam;

}

}

Solution

  • Your issue is due to the recycling of Views in ListView (and RecyclerView) The following happens to you:

    1. You set visibility of an ImageView in one row to invisible with your ToggleButton
    2. Your row gets scrolled out of sight and therefore gets recycled.
    3. The recycled view still has an ImageView with visilibility set to View.INVISIBLE
    4. You assign your new values to the row but don't change the visibility

    To solve this you should have a List, HashMap or anything similar and track which ToggleButton is checked and which ImageView is visible. (You can do that with the position)

    Then in your public View getView(int position, View convertView, ViewGroup parent) method you check if this posision's (rows) image should be visible or not and set it accordingly with viewHolder.btnCam.setVisibility() and the ToggleButton as well.

    EDIT

    Add this member variable to your adapter
    HashMap<Integer,Boolean> toggleButtonStateTracker = new HashMap<>;

    In your getView add this

    if (! toggleButtonStateTracker.containsKey(position)){
      // Now the HashMap definitely contains the key
      toggleButtonStateTracker.put(position,false);
    }
    
    boolean isChecked = toggleButtonStateTracker.get(position);
    viewHolder.tgIrregular.setChecked(isChecked);
    if (isChecked){
      // if your toggle Button is checked, the btnCam should be invisible 
      viewHolder.btnCam.setVisibility(View.INVISIBLE);
    } else {
      viewHolder.btnCam.setVisibility(View.VISIBLE);
    }
    

    And finally add this tgIrregular

    viewHolder.tgIrregular.setOnCheckedChangedListener(new CompoundButton.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        toggleButtonStateTracker.put(getAdapterPosition, isChecked);
      }
    });