Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android custom ListView repeats selection background

I have a custom adapter:

public class PhraseCustomAdapter extends BaseAdapter  
{  
public String original[];  
public String translation[];  
public String transcription[];  

public Activity context;  
public LayoutInflater inflater;  

public PhraseCustomAdapter(Activity context,String[] original, String[] translation, String[] transcription) {  
    super();  

    this.context = context;  
    this.original = original;  
    this.translation = translation;  
    this.transcription = transcription;  

    inflater = LayoutInflater.from(context);
    this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
}  

@Override  
public int getCount() {  
    // TODO Auto-generated method stub  
    return original.length;  
}  

@Override  
public Object getItem(int position) {  
    // TODO Auto-generated method stub  
     return position;
}  

public String getItemTranlation(int position) {  

    return translation[position];  
}  

public String getItemTranscription(int position) {  
    // TODO Auto-generated method stub  
    return transcription[position];  
}  

public String getItemOriginal(int position) {  
    // TODO Auto-generated method stub  
    return original[position];  
} 


@Override  
public long getItemId(int position) {  
    // TODO Auto-generated method stub  
    return position;  
}  

static class ViewHolder  
{  
    ImageView imgViewLogo;  
    TextView txtViewOriginal;  
    TextView txtViewTranslation;  
    TextView txtViewTranscription; 
}  

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

    ViewHolder holder;  
    if (convertView == null) {  
        holder = new ViewHolder();  
        convertView = inflater.inflate(R.layout.phrase_row, null);  

        holder.imgViewLogo = (ImageView) convertView.findViewById(R.id.imgViewLogo);  
        holder.txtViewOriginal = (TextView) convertView.findViewById(R.id.txtViewOriginal);  
        holder.txtViewTranslation = (TextView) convertView.findViewById(R.id.txtViewTranslation);  
        holder.txtViewTranscription = (TextView) convertView.findViewById(R.id.txtViewTranscription);   

        convertView.setTag(holder);  
    }  
    else  
        holder=(ViewHolder)convertView.getTag();  


    holder.txtViewOriginal.setText(original[position]);  
    holder.txtViewTranslation.setText(translation[position]);  
    holder.txtViewTranscription.setText(transcription[position]);

    return convertView;  
}   

}

And I need to implement android4-style multi selection (long-press, then click to select item). So:

    lview1 = (ListView) findViewById(R.id.listViewPhrase);  
    adapter = new PhraseCustomAdapter(this, original, translation, transcription);  
    lview1.setAdapter(adapter);  
    lview1.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
    lview1.setMultiChoiceModeListener(new MultiChoiceModeListener() {

        @Override
        public void onItemCheckedStateChanged(ActionMode mode, int position,
                                              long id, boolean checked) {
            View view;
            if (checked){
                Log.v ("checked?", "YES");
                Log.v ("Position", Integer.toString(position));

                view = lview1.getChildAt(position);
                view.setBackgroundColor(Color.LTGRAY);


                original_list.add (adapter.getItemOriginal(position));
                translation_list.add (adapter.getItemTranlation(position));
                transcription_list.add (adapter.getItemTranscription(position));

                countSelected += 1;
            }
            if (!checked){
                Log.v ("checked?", "NO");
                Log.v ("Position", Integer.toString(position));


                for (int i = 0; i < original_list.size(); i++)
                {
                    if (original_list.get(i) == adapter.getItemOriginal(position)){
                        original_list.remove (i);
                        translation_list.remove (i);
                        transcription_list.remove (i);  
                    }
                }
                countSelected -= 1;
            }


            mode.setTitle(Integer.toString(countSelected) + " " + getString(R.string.selectItem));
        }

The problem is: when I long-press the item (for example, first item), the 7th item is highlighted too (by changing background). When I try to "unhighlight" highlighted 7th item, app crashes. If I try to click the latest item, app crashes. I have read some articles about how View is rendered and recycle items, but I don't know any possible solution for my problem

UPD: LogCat output when "unhighlighting" 7th item:

    V/checked?(24966): YES
V/Position(24966): 7
Shutting down VM
threadid=1: thread exiting with uncaught exception (group=0x2b542210)
AndroidRuntime(24966): FATAL EXCEPTION: main
java.lang.NullPointerException
at com.alextee.phrases.PhraseActivity$1.onItemCheckedStateChanged(PhraseActivity.java:145)
at android.widget.AbsListView$MultiChoiceModeWrapper.onItemCheckedStateChanged(AbsListView.java:5688)
at android.widget.AbsListView.performItemClick(AbsListView.java:1040)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:2522)
at android.widget.AbsListView$1.run(AbsListView.java:3183)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
 android.app.ActivityThread.main(ActivityThread.java:4441)
at java.lang.reflect.Method.invokeNative(Native Method)
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(Native Method)
like image 855
Alexander Tee Avatar asked Aug 19 '12 08:08

Alexander Tee


2 Answers

List view is itself complex and it's recycling is too. If you watch the android developer videos for list view you will know more about list view recycling and it's issues.
The problem in your app is due to list view recycling.
When you change the background of a list item on click or long click then it's background changes and when this item is scrolled out of the visible area of your device the view attached with this item is recycled and it is allotted to some other list item which is currently visible. So now that item will also be highlighted.
This picture describes the list view recycling:

enter image description here


For highlighting an item in a list view you should do following:

  • Set onItemClickListener on the list view.
  • In the onItemClick() method change the background of the view and save the current highlighted position in the list view and call notifyDataSetChanged() on the list adapter. Calling notifyDataSetChanged() is important because it redraws the current visible items.

Code for list onItemClick should be something like this:

grid[pos].setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick (AdapterView<?> parent,
                    View v, int position, long Id)
            {
                    highlighted = position;    //highlighted is a global variable
                    //container is the root view of the list row layout
                    LinearLayout container = (LinearLayout)v.findViewById(R.id.container);
                    container.setBackgroundResource(R.drawable.highlighted_backg);
                    mListAdapter.notifyDataSetChanged();

            }
   });

The getView() method should be implemented like this:

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

    if(convertView == null) {
        convertView = inflater.inflate(R.layout.row_item, null);
        holder = new ViewHolder();
        holder.itemName1 = (TextView)convertView.findViewById(R.id.text1);
        ...
        holder.container = (LineaLayout)convertView.findViewById(R.id.container);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    if(MainActivity.highlighted == position) {
        holder.container.setBackgroundResource(R.drawable.highlighted_backg);
    }else {
        holder.foodItemCol1.setBackgroundResource(R.drawable.normal_back);
    }

    return convertView;
}
like image 61
karn Avatar answered Nov 04 '22 04:11

karn


Set the background color of the item in getView() according to it's checked state. I don't know if you can get this directly from the ListView, otherwise you could keep e.g. a Set and hold the currently checked positions there. Then in getView() look up if the passed position is in this set, which would mean that it is checked and set the background accordingly. If not, set the background to the unchecked color. The later one is important as you could have been given a recycled View having the background set to the 'checked' color.

An example for keeping track of checked items can be found here.

like image 40
Ridcully Avatar answered Nov 04 '22 04:11

Ridcully