Android incorrect behavior of child CheckBox - android

I have a custom ExpendableListViewAdapter. Like this:
Group
Child
Child
Child
Group
All his groups and childs have checkboxes.
I want parent CheckBox to dynamically check itself when all his children are checked.
How to implement this?
I've implemented selection of children then the parent element selected.
This is my method for it:
void selectGroup(boolean isSelected) {
for (Item itemChild : item.getItems()) {
if (isSelected) itemChild.isSelected = true;
else itemChild.isSelected = false;
itemChild.setSelected(isSelected);
}
notifyDataSetChanged();
}
And I summon it inside getGroupView:
holder.cbChild.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
item.isSelected = isChecked;
item.setSelected(isChecked);
if (getChildrenCount(groupPosition) != 0) {
selectGroup(item.isSelected);
}
}
});
And it works fine.
I've tried to implement parent selection as well.
Method:
public boolean isAllChildrenSelected(int groupPosition) {
int selectedNumber = 0;
for (int i = 0; i < getChildrenCount(groupPosition); i++) {
Item itemChild = getChild(groupPosition, i);
if (itemChild.isSelected) {
selectedNumber++;
}
}
if (selectedNumber == getChildrenCount(groupPosition)) {
notifyDataSetChanged();
return true;
} else return false;
}
And I summon this method inside my getGroupView as well:
if (getChildrenCount(groupPosition) != 0) {
if (isAllChildrenSelected(groupPosition)) {
holder.cbChild.setChecked(true);
}
}
And then, in getChildView inside setOnCheckedChangeListener for child CheckBox I summon notifyDataSetChanged();.
But I'm getting some strange behavior - for example, if group have 3 childs and I'm trying to select the 1st one, it selects the 2nd and the 3rd.
I think, this is because I putted notifyDataSetChanged(); in a wrong place, but I cannot figure out where exactly and how to fix it.

Well, it looks like I've figured it out.
Add this method:
private void selectGroupIfAllChildrenSelected() {
int numSelected = 0;
for (Child child: group.getChildrenList()) {
if (child.isSelected()) {
numSelected++;
}
if (group.getChildrenList().size() == numSelected) {
group.setSelected(true);
} else if (numSelected == 0) {
group.setSelected(false);
}
}
notifyDataSetChanged();
}
And summon it inside your getChildView method inside OnCheckedChangeListener:
holder.cbChild.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
child.setSelected(isChecked);
selectGroupIfAllChildrenSelected();
}
});
Also, don't forget to set to null your setOnCheckedChangeListener inside getChildView before you set a CheckBox state, otherwise you will get some strange behaviour:
holder.cbChild.setOnCheckedChangeListener(null);
holder.cbChild.setChecked(child.isSelected());
All this will also deselect your group CheckBox as soon as you deselect all the children.

Related

Checked checkbox outside the adapter class on listView

In my Activity i have a list view and a check box.I created an adapter and do all stuff about it.In adapter i have 4 checkbox that loaded into list view.
I want to when user check this man checkBox in activity all checkBoxs on adapter is checked so in order to In activity first i find check box and finally i override setOnCheckedChangeListener method.
To find out number of view in adapter i am using TmpAdp.getCount().TmpAdp is my instance adapter.
This is a completed code :
chb_allCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
for (int i = 0; i < TmpAdp.getCount(); i++) {
View view = Glist.getChildAt(i);
CheckBox ch_item = view.findViewWithTag(i);
// CheckBox ch_item = view.findViewById(R.id.chb_send_info); // this is not work like findViewWithTag
ch_item.setChecked(true);
}
TmpAdp.checkAll(TmpAdp.getCount());
TmpAdp.notifyDataSetChanged();
} else {
}
}
});
The problem is, when i checked mani check box (chb_allCheck), none of them of check boxs inside adapter is not checked ?
This is a part of adapter class and getView method:
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) Tmpcontext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.liststyle_saveed, parent, false);
BtnChangeToInPross = rowView.findViewById(R.id.BtnChangeToInPross);
TextView txtNosaziCodestr = rowView.findViewById(R.id.NosaziCodestr);
checkBox = rowView.findViewById(R.id.chb_send_info);
checkBox.setTag(position);
As you can see when i have clicked on Main check boxs, none of them of check box inside adapter is not checked
First of all make a Model class. In which you will take boolean isChecked.
Now in adapter class you will attach that model boolean to the list item checkbox.
checkbox.setChecked(model.isChecked());
When you check your main checkbox. Then just change isChecked boolean in your model list.
boolean isChecked = mainCheckBox.isChecked();
for(Model model : adapter.getList()){
model.setChecked(isChecked);
}
adapter.notifyDataSetChanged();
Here when you change checked status of model and notify your list. Then all your checkBox will be checked automatically, because these are attached to the model checked field.
It is logic, I did not write full code, you will make Model class, and will attach model by your list item.
Create a model class and make getter and setter method for check and uncheck Checkbox
Click of Main Checkbox in Activity
chb_allCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean
for (int i = 0; i < yourList.size; i++) {
if(checked){
yourlist.get(i).setCheckBoxAdapter(true);
}
else{
yourlist.get(i).setCheckBoxAdapter(false);
}
}
TmpAdp.notifyDataSetChanged();
}
});
in yourAdapterClass
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
CheckBoxModelClass modelclass= getItem(position);
if(modelclass.getCheckbox()){
adapterCheckBox.setChecked(true);
}
else{
adapterCheckBox.setChecked(false);
}
return convertView;
}
Tank you of your guys, You have made good suggestions but let me complete my way.
My problem was TmpAdp.notifyDataSetChanged();.When i removed this, code work completally.
I have completed my code and works perfectly and it can be an additional way to Select
all checkbox.
chb_allCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
for (int i = 0; i < TmpAdp.getCount(); i++) {
View view = Glist.getChildAt(i);
CheckBox ch_item = view.findViewById(R.id.chb_send_info);
ch_item.setChecked(true);
}
TmpAdp.checkAll(TmpAdp.getCount());
} else {
for (int i = 0; i < TmpAdp.getCount(); i++) {
View view = Glist.getChildAt(i);
CheckBox ch_item = view.findViewById(R.id.chb_send_info);
ch_item.setChecked(false);
}
TmpAdp.removeAll();
}
}
});

How to highlight the first row of RecyclerView by default and then remove highlight as and when other rows are selected?

I am trying to load a list in RecyclerView and show the first row of the list as selected. I have achieved it using the following code:
#Override
public void onBindViewHolder(NavigationDrawerAdapter.ViewHolder holder, final int position) {
if (!mNavClassrooms.get(position).equals("")) {
holder.mTextViewClassroom.setText(mNavClassrooms.get(position)); // Setting the Text with the array of our Titles
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, false));
/*
The following code was written to make the first item in the Classroom list as selected.
It leads to the item always being selected and hence has been commented out.
*/
if(position == 0 && intOldSelectedItem == -1){
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, true));
intOldSelectedItem = 0;
mSelectedView = holder.mRelLayClassroom.getChildAt(position);
mSelectedItems.put(position, true);
}
else{
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, false));
}
} else {
holder.mTextViewClassroom.setText("No classes found");
holder.mTextViewClassroom.setPadding(40, 0, 0, 0);
}
holder.mRelLayClassroom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mSharedPreferences = mContext.getSharedPreferences(Constants.AAPREFERENCES, Context.MODE_PRIVATE);
String strClassroomValue = mNavClassrooms.get(position);
int strClassroomName = mNavClassroomNames.get(position);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.CLASSROOM_VALUE, strClassroomValue);
editor.putInt(Constants.CLASSROOM_NAME, strClassroomName);
editor.commit();
/*
We are storing the position of the selected row in the SparseBooleanArray.
We delete it in case another row has been selected.
*/
if (mSelectedItems.get(position, false)) {
/*
Do nothing
*/
} else {
mSelectedItems.put(position, true);
/*
Making sure that the delete code is called only if some view is selected
*/
if (mSelectedView != null) {
mSelectedView.setSelected(false);
mSelectedItems.delete(intOldSelectedItem);
view.setSelected(false);
}
mSelectedView = view;
intOldSelectedItem = position;
view.setSelected(true);
}
}
However, now the first row stays selected always. I am unable to deselect it. I cannot seem to get this working.
I referred to the following answer to achieve most of this functionlaity.
https://stackoverflow.com/a/29984220/2186220
Any help will be appreciated.
I'm not answering your question by posting a fixed version of your onBindViewHolder method since it's kinda hard to understand and we don't know how the rest of your adapter looks like. So following a RecyclerView Adapter which does what you want: Selecting the first row by default and deselecting it once a other row is selected.
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// ... other fields
// default selection position is the first one
private int selectedPosition = 0;
// ... constructor etc.
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
if(position == selectedPosition){
holder.itemView.setSelected(true);
} else {
holder.itemView.setSelected(false);
}
// Actual selection / deselection logic
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int currentPosition = holder.getLayoutPosition();
if(selectedPosition != currentPosition){
// Temporarily save the last selected position
int lastSelectedPosition = selectedPosition;
// Save the new selected position
selectedPosition = currentPosition;
// update the previous selected row
notifyItemChanged(lastSelectedPosition);
// select the clicked row
holder.itemView.setSelected(true);
}
}
});
// other adapter code
}
// other adapter stuff like onCreateViewHolder, getItemCount, ViewHolder etc.
}
Note: I guess there's no need to use a SparseBooleanArray so simply remove it and replace it with the int field used in the example above.
Initialize your
int intOldSelectedItem=0 and keep one boolean isVisible= false;
And do it as below:
if (holder.getPosition() == intOldSelectedItem) {
if (isVisible) {
//background for selected item
} else {
//background for unselected item
}
} else {
//background for unselected item
}
holder.mRelLayClassroom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (intOldSelectedItem== holder.getPosition()) {
isVisible = !isVisible;
} else {
if (intOldSelectedItem!= 0) {
isVisible = false;
notifyItemChanged(intOldSelectedItem);
}
isVisible = true;
}
intOldSelectedItem= holder.getPosition();
notifyItemChanged(intOldSelectedItem);
}
});
I hope it might help you.
Add background selector to your ViewHolder layout.
Create your selector handler something like this:
public class SingleSelector {
private View oldVIew;
public void setSelection(View newView) {
if (oldVIew == null) {
newView.setSelected(true);
oldVIew = newView;
} else {
oldVIew.setSelected(false);
newView.setSelected(true);
oldVIew = newView;
}
}
}
Set default selection when you need it:
#Override
public void onBindViewHolder(SimpleViewHolder holder, int position) {
if (position == 0) {
singleSelector.setSelection(holder.itemView);
}
}
In your ViewHolder add listener to itemView and pass it to the handler:
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
singleSelector.setSelection(itemView);
}
});

Getting the checked states of a radio button in all the items in a recycler view

I have a RecyclerView which holds 10 views each having a CheckBox. Now, in my main activity when a menu button named "POST" is pressed I want to know whether all the CheckBox in each of the views of the RecyclerView is checked or not.
How can I implement this?
I advise you to pass additional variable isChecked inside every model of a list of models passed to RecyclerView.
Like that:
public class Model {
private boolean isChecked;
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
}
Then inside your RecyclerView ViewHolder, create a constructor:
public ListViewHolder(View view) {
super(view);
switchCompat = (SwitchCompat) view.findViewById(R.id.add_switch);
switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
getItemAt(getLayoutPosition()).setChecked(isChecked);
}
});
}
Then to get all button states just iterate through the list of models in Your activity:
public boolean areAllChecked() {
for (int i = 0; i < adapter.getItemCount(); i++) {
Model model = adapter.getItemAt(i);
if (!model .isChecked()) {
return false;
}
}
return true;
}
Add a boolean isChecked in your model class (Item which you are adding to RecyclerView Adapter)
In your adapter onBindViewHolder implement onCheckedChangeListener for your checkbox and set isChecked appropriately.
implement a isChecked(int position) method in your adapter. It should return checked value of the items specified by position
in your fragment/activity iterate through adapter items and find out if all items are checked or not.
Track the state of each checkbox in the adapter.
To do so let it have an map that stores a boolean value bound to a unique key for each checkbox. And also let it implement the OnCheckedChangeListener.
MyAdapter extends RecyclerView.Adapter implements OnCheckedChangeListener {
private Map<Object, Boolean> checkboxStates = new HasMap<>();
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
checkboxStates.put(buttonView.getTag(), isChecked);
}
public Map<Object, Boolean> getCheckboxStates() {
//We don't want the caller to modify adapter state.
return Collections.unmodifiableMap(this.checkboxStates);
}
//other code
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//your code
CheckBox cb;
//assuming that we have the reference to the cb view
//also assuming that cb has a unique identifiable tag assigned from the model
cb.setOnCheckedChangeListener(this);
if (this.checkboxStates.containsKey(cb.getTag()) {
cb.setChecked(this.checkboxStates.get(cb.getTag());
} else {
this.checkboxStates.put(cb.getTag(), cb.isChecked());
}
}
}
With this you can call the getCheckboxStates to get the states of each checkbox that was visible.
The key point here is that you need something unique identifiable for each item in the dataset that can be used as a tag for each checkbox that represents that item.

Dynamically add and hiding textViews

I've got "for each" loop that creates textView for every obiect from the list and add them to the linear layout. It works great. Then I want to hide all of the textViews when user clicks on toggle button but here I've got problem - only the last textView from the list is being hidden , rest of them is still visible. I tried to solve this problem with many solutions ( for example with getChild()), but nothing works.
final List<FilterItem> filterItemList = filterData.getFilterItemList();
for (FilterItem filterItem : filterItemList) {
final TextView filter = new TextView(MainPanelActivity.this);
filter.setText(filterItem.getFilterItemName());
filter.setTextColor(R.color.black);
linearLayout.addView(filter);
filter.setVisibility(View.GONE);
textLine.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
filter.setVisibility(View.VISIBLE);
} else {
filter.setVisibility(View.GONE);
}
}
});
Note that youre setting a listener for textLine inside the for loop - so for each iteration you set a new listener that changes the visibility of the TextView created in the current iteraton.
Move textLine.setOnCheckedChangeListener() outside of the for loop; and inside onCheckedChanged - loop through all children of linearLayout and set the visibility for each child.
textLine.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
for (View v : linearLayout.getChildren()) {
if (v instanceof TextView) {
if (isChecked) {
v.setVisibility(View.VISIBLE);
} else {
v.setVisibility(View.GONE);
}
}
}
You could keep a list of TextViews when you create them. Then set the click listener outside the for-loop as Dmitri says, which would iterate through the list of TextViews and set the visibility to GONE.
private void setup() {
List<View> textViews = new ArrayList<>();
for (FilterItem filterItem : filterData.getFilterItemList()) {
View view = createTextViewFor(filterItem);
linearLayout.addView(filter);
textViews.add(view);
}
updateVisibility(textViews, View.GONE);
textLine.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int visibility = isChecked ? View.VISIBLE : View.GONE;
updateVisibility(textViews, visibility);
}
}
);
}
private View createTextViewFor(FilterItem filterItem) {
TextView view = new TextView(MainPanelActivity.this);
view.setText(filterItem.getFilterItemName());
view.setTextColor(R.color.black);
view.addFilter(filterItem);
return view;
}
private void updateVisibility(List<View> views, int visibility) {
for (View view : views) {
view.setVisibility(visibility);
}
}
In case you want to display textView dynamically, I think putting textView inside RecyclerView will be a better idea. RecyclerView has its own Adapter which will make it easy to play with data and textViews. Give recyclerView a try. RecycerView for what you are trying to do can be learnt in few hours and sky is the limit for what you can do with recyclerView :)

Problem with checking all checkboxes

I have 2 problems.
The 1st problem.
I have a CheckedTextView "Select All" item and a list of array items set in a adapter of MutipleChoice. I am current trying to set the state of all the checkboxes of the array items to true/false by selecting the CheckedTextView. It works but there's a bug. Such that when the last item of the array is true, the "SelectAll" to true will work, but if the last item of the array is false, it will uncheck all the items. What I want is even if any of the item's state is false, it will be set to true when the checkedtextview is selected to true.
The 2nd problem.
I can't seem to set the checkedtextview to true when all of the array item's state is true.
Could it be that I'm doing it the wrong way? Help given would be appreciated..
This is my method for the CheckedTextView "Select All"
private void markAll (Boolean state) {
for (int i = 0; i < lv.getCount(); i++) {
lv.setItemChecked(i, state);
}
}
This is the coding for checking of the states of the array items
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
for(int i = 0; i < lv.getCount(); i++) {
//if even 1 of the item is false, the checkedtextview will be set to false
if(lv.isItemChecked(i) == false) {
ctv.setChecked(false);
}
//if all of the item is true, the checkedtextview will be set to true as well
//the coding should be done in the if-else below, if i'm not wrong
if(...) {
}
}
}
EDIT
This is the new code for calling my method
ctv.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
ctv.setClickable(true);
if(ctv.isPressed() && ctv.isChecked() == false) {
ctv.setChecked(true);
markAll(true);
}
else {
ctv.setChecked(false);
markAll(false);
}
}
});
This is my xml code for the checkedtextview
<CheckedTextView
android:id="#+id/checkedtext"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:text="Select all"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:clickable="true"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:paddingLeft="6dip"
android:paddingRight="6dip"/>
Try this reworked logic:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
ctv.setChecked(true); // assume true at first
// if even 1 of the item is false, the checkedtextview will be set to false
for(int i = 0; i < lv.getCount(); i++) {
if(lv.isItemChecked(i) == false) {
ctv.setChecked(false);
break; // exit loop if you have found one false
}
}
}
This will make your ctv have the checked state if all of the items in the list are checked, otherwise if any are false then ctv will have the unchecked state.
To control what happens when you click ctv itself:
ctv.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(ctv.isChecked()) {
markAll(true); // ctv is checked, so make all others checked
}
else {
markAll(false); // ctv is unchecked, so make all others unchecked
}
}
});
You don't need to check state of other items here - if it is just a '(de)select all' button then it should just propagate its own state to the other items.

Categories

Resources