2

I am new to android and I have just started programming a simple app to try different things out.

I was programming a ListView (and, in the same way a GridView) but there is something I got wrong. Each item is a couple of an image and a text field.

| img | __text__ |

I want to be able to choose any number of list items, keeping them enlightened for all the selection process, before passing the selected items to the next activity. If I want to de-select one of them, I simply have to re-click on the item to have the selection disappear. For this purpose I use a custom selector so that when the item is pressed it changes colours.

If the items are all contained in a screen, everything is ok. But as soon as they grow in number and recycling kicks in, the enlightening of selected items which get out of the screen is lost. I have debugged the state of items and those whose enlightening is lost are still correctly selected, so I think it’s just a problem on how the graphic reloads when an item is restored after it went out of the device screen.

Here’s the code of the activity layout:

<!-- items_selection.xml -->    

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/Background">

<ListView
    android:id="@+id/item_list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:divider="@color/divider"
    android:dividerHeight="3dp"
    android:choiceMode="multipleChoice"
    android:listSelector="@drawable/list_selector">
</ListView>

</LinearLayout>

This is the Row Item layout:

<!-- list_row.xml --> 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:orientation="horizontal"
android:padding="5dip" >

<LinearLayout
    android:id="@+id/item_list_item"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_marginRight="5dip"
    android:padding="3dip" >
    <ImageView
        android:id="@+id/item_image"
        android:layout_width="@dimen/img_side"
        android:layout_height="@dimen/img_side" />
</LinearLayout>

<TextView
    android:id="@+id/item_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@+id/item_list_item"
    android:layout_centerVertical="true"
    android:textColor="@color/black"
    android:textSize="@dimen/textnorm"
    />

</RelativeLayout>

This is the selector I used:

<!-- list_selector.xml -->

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item
    android:state_selected="false"
    android:state_pressed="false"
    android:drawable="@drawable/rect" />

<item
    android:state_pressed="true"
    android:drawable="@drawable/rect_sel" />

<item
    android:state_selected="true"
    android:state_pressed="false"
    android:drawable="@drawable/rect_sel" />

</selector>


<!-- rect.xml -->

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<gradient
    android:startColor="#D5DDE0"
    android:centerColor="#e7e7e8"
    android:endColor="#CFCFCF"
    android:angle="270" />

</shape>


<!-- rect_sel.xml -->

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<gradient
    android:startColor="#78DDFF"
    android:centerColor="#16cedb"
    android:endColor="#09adb9"
    android:angle="270" />

</shape>

This is the code of the Activity:

public class ItemSelection extends AppCompatActivity {

private int numitems;
private ListView listview;
private ArrayList<Item> items = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.items_selection);

    numitems = 15;
    build_list();

    listview = (ListView) findViewById(R.id.item_list);
    listview.setAdapter(new ListAdapter(this, items));

}

public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.next_btn, menu);
    return true;
}

    public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    switch(id){
        case R.id.next_btn:
            Intent intent = new Intent (this, nextActivity.class);
            intent.putStringArrayListExtra("items", Chosen_Items());
            startActivity(intent);
            return true;
        default:
            Toast.makeText(getApplicationContext(), "ERROR", Toast.LENGTH_SHORT).show();
            return super.onOptionsItemSelected(item);
    }
}

private void build_list() {
     //Populates the item list with more items than the screen can support.
    }


private ArrayList<String> Chosen_Items(){

    ArrayList<String> selitems = new ArrayList<>();

    for (int i=0; i<numitems; i++){
        if (items.get(i).isSelected()){
            selitems.add(items.get(i).getName());
        }
    }

    return selitems;
}

This is the code of the listAdapter:

public class ListAdapter extends BaseAdapter {

private ArrayList <Item> items;
private Activity sActivity;

public ListAdapter(Activity sActivity, ArrayList<Item> items) {
    this.sActivity = sActivity;
    this.items = items;
}

@Override
public int getCount() {
    return items.size();
}

@Override
public Object getItem(int position) {
    return items.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}


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

    View view = convertView;
    ViewHolder holder;

    if(view == null) {
        LayoutInflater li = sActivity.getLayoutInflater();
        view = li.inflate(R.layout.list_row, null);

        holder = new ViewHolder();
        holder.text = (TextView)view.findViewById(R.id.item_name);
        holder.img = (ImageView)view.findViewById(R.id.item_image);

        view.setTag(holder);
    }

    else {
        holder = (ViewHolder)view.getTag();
    }

    holder.text.setText(items.get(position).getName());
    holder.img.setImageResource(items.get(position).getImage());


    view.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View viewitem) {

            if (!viewitem.isSelected() && !items.get(position).isSelected()) {
                viewitem.setSelected(true);
                items.get(position).setSelected(true);
            }

            else {
                viewitem.setSelected(false);
                items.get(position).setSelected(false);
            }
        }

    });

    return view;
}

private static class ViewHolder{
    public TextView text;
    public ImageView img;
}

}

I have already tried to manually set the background color of the items re-entering the screen (by using

view.setBackgroundResource(R.drawable.rect_sel)

in the adapter, before the click handler) but the problem remains. Can anyone help me solving the problem?

~~~~~~~~~~~~~~~~~~~ SOLUTION ~~~~~~~~~~~~~~~~~~

It seems the selector doesn't follow the recycle of the items and their views.There has to be a better and more elegant solution taking advantage of a selector in this situation. But out of all the attempts i made, none has worked. This solution is the best workaround and does not use the selector.

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

View view = convertView;
ViewHolder holder;

if(view == null) {
    LayoutInflater li = sActivity.getLayoutInflater();
    view = li.inflate(R.layout.list_row, null);

    holder = new ViewHolder();
    holder.text = (TextView)view.findViewById(R.id.item_name);
    holder.img = (ImageView)view.findViewById(R.id.item_image);

    view.setTag(holder);
}

else {
    holder = (ViewHolder)view.getTag();
}

holder.text.setText(items.get(position).getName());
holder.img.setImageResource(items.get(position).getImage());

if(items.get(position).isSelected()){
    view.setBackgroundResource(R.drawable.rect_sel);
}else{
    view.setBackgroundResource(R.drawable.rect);
}

view.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View viewitem) {

        if (!viewitem.isSelected() && !items.get(position).isSelected()) {
            viewitem.setBackgroundResource(R.drawable.rect_sel);
            viewitem.setSelected(true);
            items.get(position).setSelected(true);
        }

        else {
            viewitem.setBackgroundResource(R.drawable.rect);
            viewitem.setSelected(false);
            items.get(position).setSelected(false);
        }
    }
    });

    return view;
}

private static class ViewHolder{
    public TextView text;
    public ImageView img;
}

While in the list_row.xml file, the following line can be just deleted:

android:background="@drawable/list_selector"
Panda_Zero
  • 23
  • 1
  • 4

3 Answers3

0

You must define the current selection state of view inside getView method.

Add this line:

viewitem.setSelected(items.get(position).isSelected());

after viewholder has been created like:

holder.img.setImageResource(items.get(position).getImage());
viewitem.setSelected(items.get(position).isSelected());
subhash
  • 2,689
  • 18
  • 13
0

In your getView() method just add this test:

if (items.get(position).isSelected()){
   view.setBackgroundResource(R.drawable.rect_sel);
} else {
   view.setBackgroundResource(R.drawable.rect);
}

Or just view.setSelected(items.get(position).isSelected());. While you already have a selector for your list item.

Rami
  • 7,879
  • 12
  • 36
  • 66
  • By using your first solution, I had indeed some changes: Now when I select an item, it doesn't enlight. But as soon as i scroll down and then up again, it is enlighted. I think this can work with some workaround, but why the selector solution just doesn't work? I would expect it to recognize the state of the recycled item, but it appears it just gets ignored, even if i put `view.setSelected(items.get(position).isSelected());` – Panda_Zero Sep 30 '15 at 08:20
  • The selector is not working because "setBackgroundResource()" will remove the selector and put the new drawable as background. If you want to sue the first solution, you can remove definitely the selector from background and use setBackgroundResource() inside the click listener. – Rami Sep 30 '15 at 08:58
  • Yes, `view.setBackgroundResource("gradient");` overwrites the selector. The thing i don't understand is why the selector itself does not react to the recycle of items even if I explicitly tell it with `view.setSelected(items.get(position).isSelected());`. I even tried to reload it with `view.setBackgroundResource(R.drawable.list_selector);` but nothing changes. – Panda_Zero Sep 30 '15 at 09:14
0

I think you should set the id for your RelativeLayout then add it to ViewHolder

private static class ViewHolder{
    public TextView text;
    public ImageView img;
    RelativeLayout rl;
}

After that you handle event when click RelativeLayout then change background for RelativeLayout

 public View getView(...)
    ...
    ...
    // you should update the state of relative layout first
     if (items.get(position).isSelected()) {
        holder.setBackgroundColor(Color.parseColor("#ffff00"));
    }else{
         holder.setBackgroundColor(Color.parseColor("#ff0000"));
    }

    holder.rl.setOnClickListener(new View.OnClickListener(){ //remmember it is rl.setOnClick... not view.setOnClick...
        public void onClick(View v) {
              if (!items.get(position).isSelected()) {
                  items.get(position).setSelected(true);
                  holder.setBackgroundColor(Color.parseColor("#ffff00"));
              }else {
                  items.get(position).setSelected(false);
                  holder.setBackgroundColor(Color.parseColor("#ff0000"));
              }
        }
    });
}

Suggestion
You should modify your row layout like this (I have removed LinearLayout but the new layout still good)

    <!-- list_row.xml --> 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:orientation="horizontal"
android:padding="5dip" >

  <ImageView 
     android:id="@+id/item_image"
     android:layout_marginRight="5dip"
     android:padding="3dip"
     android:layout_alignParentLeft="true"
     android:layout_width="@dimen/img_side"
     android:layout_height="@dimen/img_side" />

<TextView
    android:id="@+id/item_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@+id/item_image"
    android:layout_centerVertical="true"
    android:textColor="@color/black"
    android:textSize="@dimen/textnorm"
    />

</RelativeLayout>

Remember that your list row layout is more simple then your listview will scroll faster, smooth and prevent some annoying bug.

Hope this help

Linh
  • 57,942
  • 23
  • 262
  • 279
  • When I try to access `holder.rl.setSelected(true);` it gives me "Variable holder is accessed from within inner class, needs to be declared final". So I made it final, but the problem still remains. – Panda_Zero Sep 30 '15 at 08:14
  • @Panda_Zero problem only generate when you scroll, right? if possible could you update your full adapter source code again – Linh Sep 30 '15 at 08:16
  • I just updated the code I changed in the adapter with your solution. – Panda_Zero Sep 30 '15 at 08:44
  • @Panda_Zero i have modify my code, please check it. thank you – Linh Sep 30 '15 at 08:53
  • Yes i miscopied the listener signature, I used `holder.rl.setOnClickListener(..)` in my code. Anyway, applying again your suggested modifications, nothing happens: the selected items still turn-off whent hey exit the screen and wont be enlightned when they come in again. – Panda_Zero Sep 30 '15 at 09:06
  • I also changed some code inside onClick(). did you changed?. i see you only change view -> holder.rl – Linh Sep 30 '15 at 09:13
  • I updated the code with all your latest modifications. The only different thing is in the **if() else** construct, as I had to access the item of the holder to correctly use it. Anyway, the problem persists even in this case. (PS: thanks for the tip on the list_item!) – Panda_Zero Sep 30 '15 at 09:29
  • @Panda_Zero if you don't mind could you try the new code that i have updated :). thank you – Linh Sep 30 '15 at 09:48
  • This last solution is the right workaraound. It seems that the selector doesn't follow the recycle of the items and their views.There has to be a better and more elegant solution taking advantage of a selector in this situation. But out of all the attempts i made, none has worked. – Panda_Zero Sep 30 '15 at 10:07