0

The problem: When I clear and repopulate my ListView, if an item was previously checked, the item at the same index is checked as well. If the number of elements added to the adapter < the index of the previously checked item, nothing is checked.

I've tried blocking the use of convertView even if it exists but that has no effect. Strangely, when I stepped through the debugger to see what is going on and I see that repopulating the adapter results in one or more calls to setChecked(true) in CheckableLinearLayout. (I've included that stack trace below) Looking at the call stack for these invocations shows that its not any code that I wrote originating these calls. It's as though the ListView is remembering the index of the previous selection and restoring it on it's own.

My implementation: I've got a ListView set to singleSelection mode. I use a custom ArrayAdapter that has a getView(...) which returns instances of my CheckableListView implementation:

public class CheckableLinearLayout extends LinearLayout implements Checkable {

   private boolean isChecked;

   public CheckableLinearLayout(Context context) {
      super(context);
   }

   public CheckableLinearLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
   }

   @Override
   public void setChecked(boolean isChecked) {
      this.isChecked = isChecked;
      if(isChecked) {
         setBackgroundColor(Color.DKGRAY);
      } else {
         setBackgroundColor(Color.BLACK);
      }
   }

   @Override
   public boolean isChecked() {
      return isChecked;
   }

   @Override
   public void toggle() {
      setChecked(!isChecked);
   }
}

My ArrayAdapter's getView implementation:

@Override
public View getView(int pos, View convertView, ViewGroup parent) {

         LayoutInflater inf = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

         CheckableLinearLayout row = (CheckableLinearLayout) convertView;
         if (row == null) {
            row = (CheckableLinearLayout) inf.inflate(R.layout.file_explorer_item, parent, false);
         }

         // I explicitly set checked to false, in case this was a convertView:
         row.setChecked(false);

         // population various elements of the view is done here

         return row;
      }
   }

Any ideas?

PS. Here's the stack trace of one of the mystery calls to setChecked(true):

<1> main@830013232224, prio=5, in group 'main', status: 'RUNNING'
      at my.package.CheckableLinearLayout.setChecked(CheckableLinearLayout.java:25)
      at android.widget.ListView.setupChild(ListView.java:1834)
      at android.widget.ListView.makeAndAddView(ListView.java:1765)
      at android.widget.ListView.fillSpecific(ListView.java:1318)
      at android.widget.ListView.layoutChildren(ListView.java:1636)
      at android.widget.AbsListView.onLayout(AbsListView.java:1863)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1617)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1401)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
      at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
      at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
      at android.view.View.layout(View.java:11278)
      at android.view.ViewGroup.layout(ViewGroup.java:4224)
      at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1489)
      at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2442)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loop(Looper.java:137)
      at android.app.ActivityThread.main(ActivityThread.java:4424)
      at java.lang.reflect.Method.invokeNative(Method.java:-1)
      at java.lang.reflect.Method.invoke(Method.java:511)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
      at dalvik.system.NativeStart.main(NativeStart.java:-1)

<10> Binder Thread #2@830019920808, prio=5, in group 'main', status: 'RUNNING'
      at dalvik.system.NativeStart.run(NativeStart.java:-1)

<9> Binder Thread #1@830019917952, prio=5, in group 'main', status: 'RUNNING'
      at dalvik.system.NativeStart.run(NativeStart.java:-1)

<8> FinalizerWatchdogDaemon@830019904352 daemon, prio=5, in group 'main', status: 'SLEEPING'
      at java.lang.VMThread.sleep(VMThread.java:-1)
      at java.lang.Thread.sleep(Thread.java:1031)
      at java.lang.Thread.sleep(Thread.java:1013)
      at java.lang.Daemons$FinalizerWatchdogDaemon.run(Daemons.java:213)
      at java.lang.Thread.run(Thread.java:856)

<7> FinalizerDaemon@830019904008 daemon, prio=5, in group 'main', status: 'WAIT'
      at java.lang.Object.wait(Object.java:-1)
      at java.lang.Object.wait(Object.java:401)
      at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:102)
      at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:73)
      at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:168)
      at java.lang.Thread.run(Thread.java:856)

<6> ReferenceQueueDaemon@830019903648 daemon, prio=5, in group 'main', status: 'WAIT'
      at java.lang.Object.wait(Object.java:-1)
      at java.lang.Object.wait(Object.java:364)
      at java.lang.Daemons$ReferenceQueueDaemon.run(Daemons.java:128)
      at java.lang.Thread.run(Thread.java:856)

<5> Compiler@830019903408 daemon, prio=5, in group 'system', status: 'WAIT'
      at dalvik.system.NativeStart.run(NativeStart.java:-1)

<3> Signal Catcher@830019902928 daemon, prio=5, in group 'system', status: 'WAIT'
      at dalvik.system.NativeStart.run(NativeStart.java:-1)

<2> GC@830019902704 daemon, prio=5, in group 'system', status: 'WAIT'
      at dalvik.system.NativeStart.run(NativeStart.java:-1)
Nick
  • 8,181
  • 4
  • 38
  • 63
  • 1
    Afaik setChecked would be called (if there is Checkable interface) by ListView with value from getCheckedItemPosition(s) (set with ListView setItemChecked) after getView. Maybe internal container is not cleared when the adapter is cleared or changed. Uncheck the item when required. – Deucalion Apr 25 '12 at 05:59
  • do you call `setChoiceMode(ListView.CHOICE_MODE_SINGLE);` in your `ListView`? In my experience, the ListView in CHOICE_SINGLE_MODE would handle such problem by itself. – Longerian Apr 25 '12 at 06:05
  • @Longerian: Nope - I do that in the xml style applied to my ListView. The style applied looks like this: I know there have been reports of this setting being ignored when set via XML but a quick test showed that if I comment it out, then single selection mode isnt enabled and items are not checked when clicked. – Nick Apr 25 '12 at 06:08
  • @Deucalion: I think you're on to something! When repopulating I added the following and things appear to be working as expected: int checkedPos = getListView().getCheckedItemPosition(); getListView().setItemChecked(checkedPos, false); – Nick Apr 25 '12 at 06:16
  • It's official - that did the trick! Thx Deucalion. If you feel like moving your comment into an answer I'll accept it. – Nick Apr 25 '12 at 06:32

4 Answers4

5

You can use this CheckableLinearLayout

public class CheckableLinearLayout extends LinearLayout implements Checkable {
boolean mChecked = false;

private static final int[] CHECKED_STATE_SET = {
        R.attr.state_checked
};

public CheckableLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean isChecked() {
    return mChecked;
}

@Override
public void setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        refreshDrawableState();
    }
}

@Override
public void toggle() {
    mChecked = !mChecked;
    refreshDrawableState();
}

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    if (isChecked()) {
        mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    }
    return drawableState;
}

@Override
public boolean performClick() {
    toggle();
    return super.performClick();
}

}

performClick is overriden - so it changes it's status on click; remove it if you don't need it

for background color you can use something like this:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <shape android:shape="rectangle">
            <solid android:color="#e13f2e"/>
        </shape>
    </item>
    <item android:state_checked="false">
        <shape android:shape="rectangle">
            <solid android:color="#ffffff"/>
        </shape>
    </item>
</selector>
babay
  • 4,689
  • 1
  • 26
  • 40
0

This is because every time when you move your items in listview then it recreates its each row but as you have not saved the state so on recreation it removes its state and set it to its default state. I have already posted an answer related to that you can check hope it will be helpful.

How to implement a button that gets all checkbox's state and adds the value of checked item into arraylist?

Community
  • 1
  • 1
Bharat Sharma
  • 3,926
  • 2
  • 17
  • 29
  • My problem is actually the opposite: I dont want the listview to preserve state when I recreate. Furthermore, the default state of isChecked is false (default for boolean) and on top of that, my implementation of getView() explicitly sets it to false, in case a recycled view is used. – Nick Apr 25 '12 at 05:34
  • I am not able to understand what you want to do if you dont want to preserve or save your state then you no need to save it just set it accordingly. – Bharat Sharma Apr 25 '12 at 05:53
  • I'm trying to say that I am setting it accordingly but something is overwriting what I am setting. The debugger is showing setChecked(true) is being invoked, although by what I don't know. – Nick Apr 25 '12 at 05:56
  • In all of your cases you need to save your states because if it recreates row in listview then also it randomly set your state of checkboxes. – Bharat Sharma Apr 25 '12 at 05:57
  • That's why I have the explicit call to setChecked(false) in getView(). – Nick Apr 25 '12 at 06:03
  • you have not called super.setChecked(ischecked); in your overrided set checked method can you try it – Bharat Sharma Apr 25 '12 at 06:33
0

setChecked would be called (if there is Checkable interface) by ListView with value from getCheckedItemPosition(s) (set with ListView setItemChecked) after getView. Maybe internal container is not cleared when the adapter is cleared or changed. Uncheck the item when required.

I suggest to use state list drawable as well. onCreateDrawableState, mergeDrawableState, refreshDrawableState, state_checked. Just google.

Deucalion
  • 196
  • 1
  • 7
0

Here is a good implementation of CheckableRelativeLayout : link

Also, setting listView SINGLE_CHOICE (or multiple) in layout/xml didn't work for me, used this instead after setting adapter

    myListView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119