简体   繁体   中英

Different select colors for different list view items

I have the following requirements:

  • different colors for different list view items
  • colors are specified dynamically in the code
  • color should only be shown if the list view item is pressed/selected
  • color of the list view item should not change permanently

For whatever reasons it seems not to be as straight forward as I thought. The only solution that goes at least a little bit in the right direction is this one: https://stackoverflow.com/a/16978159/658718

The caveat is, that this does not change the on select color, but changes the background color permanently, plus it already changes the background color for list view items if you scroll down a bit.

How can I approach this?

I'd say go with state-aware drawables . Create a state-aware drawable XML file for each of the colors you want the background of your single ListView to be. Here is an example of state- aware Drawables called background_black.xml and background_green.xml. They makes your default background color white and while pressed/selected temporarily changes it to black or green. Both of these files go in your Drawable folder.

background_black.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:drawable="@android:color/white" />
    <item android:state_pressed="true" android:color="@android:color/black" />
    <item android:state_selected="true" android:color="@android:color/black" />
</selector>

background_green.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:drawable="@android:color/white" />
    <item android:state_pressed="true" android:color="@android:color/green" />
    <item android:state_selected="true" android:color="@android:color/green" />
</selector>

In your ListView item xml file, assign an ID to your root layout or whichever element is providing the visible background color. For this example, Ill assume its your root layout. Then in your Adapter's getView(), grab the element that you've assigned the id to and set one of the drawables you created with the color you want for its background. Like so:

@Override
public View getView(int position, View convertView, ViewGroup parent){
     //inflate your convertView, etc...
     ...
     ViewGroup baseLayout = (ViewGroup)convertView.findViewById(R.id.<your base layout id>);

     //these conditions need to reflect how you decide which list item gets which color
     if(position % 2 == 0){
         baseLayout.setBackground(R.drawable.background_black);
     } else {
         baseLayout.setBackground(R.drawable.background_green);
     //do whatever else you need
     ...
     return convertView;
}

NOTE: setBackground() is a new function, use setBackgroundDrawable() if coding for older versions of Android

The difficulty here is that pressed/checked color is dynamic. You cannot use static xml color-state-list. But you can create ColorStateList by code. Here is how to do that.

You just have to implement the ListAdapter :

private class MyListAdapter implements ListAdapter{

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView!=null){
             CheckedTextView textView = (CheckedTextView)convertView;
             textView.setText("the text for item "+position);
             textView.setTextColor(makeColorStateListForItem(position));
             return textView;
        }else{
             CheckedTextView textView = new CheckedTextView(parent.getContext());
             textView.setText("the text for item "+position);
             textView.setTextColor(makeColorStateListForItem(position));
             return textView;
        }
    }

    private ColorStateList makeColorStateListForItem(int position){
        int pressedColor = pressedColorForItem(position);
        int checkedColor = checkedColorForItem(position);
        int defaultColor = defaultColorForItem(position);
        ColorStateList colorStateList = new ColorStateList(
                new int[][]{
                        new int[]{android.R.attr.state_pressed},
                        new int[]{android.R.attr.state_checked},
                        new int[]{0},
                },
                new int[]{
                        pressedColor, //use when state is pressed
                        checkedColor, //use when state is checked, but not pressed
                        defaultColor}); //used when state is not pressed, nor checked 
    }

    private int pressedColorForItem(int position){
        //write your business logic to determine color here
        return ...;
    }

    private int checkedColorForItem(int position){
        //write your business logic to determine color here
        return ...;
    }

    private int defaultColorForItem(int position){
        return Color.WHITE;
    }

    //all other adapter methods
    //...

Note the use of android.R.attr.state_checked instead of the more intuitive android.R.attr.state_selected because the state_selected is not very precisely define with a touch screen (ie state_selected can give the expected behavior on the emulator, but on a real device it will probably fail)

On the other hand state_checked + CheckedTextView is going to work correctly on both simulator and real device.

Just call List.setChoiceMode(...); when initializing listView and ListView.setItemChecked in the clickListener.

final ListView listView = ...
listView.setAdapter(new MyListAdapter());
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        listView.setItemChecked(position,true);
    }
});

EDIT : to change the item background : just create a StateListDrawable instead of a simple ColorStateList :

private Drawable makeBackgroungForItem(int position){
    int pressedColor = pressedBackgroundColorForItem(position);
    int checkedColor = checkedBackgroundColorForItem(position);
    int defaultColor = defaultBackgroundColorForItem(position);
    StateListDrawable stateListDrawable = new StateListDrawable();
    stateListDrawable.addState(new int[]{android.R.attr.state_list_pressed}, new ColorDrawable(pressedColor));
    stateListDrawable.addState(new int[]{android.R.attr.state_list_checked}, new ColorDrawable(checkedColor));
    stateListDrawable.addState(new int[]{0, new ColorDrawable(defaultColor));
    return stateListDrawable;
}

And in the getView(...)

textView.setBackground(makeBackgroungForItem(position));

I suggest the following approach:

  • You need to create a ListView Adapter that supports multiple different items
  • Each different Item class represents a different color and can have it's own implementation of how the pressed or selected state should be handled
  • Since every ListItem will have it's own .xml layout file, you can specify the desired selector there

What you need:

  • A baseclass ListItem that every item in the ListView inherits from
  • This class provides abstract methods to get the View that represents the item and it's type
  • If required, the ListItem class could have an Integer field mColor, that holds the color the item represents
  • If required, the ListItem class could have a method to setup the selector with the speficied color

Example:

public abstract class ListItem {

    public static final int TYPE_WHATEVER_1                     = 0;
    public static final int TYPE_WHATEVER_2                     = 1;
    // and so on...

    /** the total number of list-item-types */
    public static final int TYPE_COUNT              = typecounthere;

    // if required for your implementation:
    protected int mColor;

    public abstract int getViewType();
    public abstract View getView(LayoutInflater inflater, View convertView);

    /** creates and sets the selector with your specified color */
    public void setupSelectorColor() {

        StateListDrawable states = new StateListDrawable();

        ColorDrawable cdPressed = new ColorDrawable(mColor);
        ColorDrawable cdSelected = new ColorDrawable(mColor);
        ColorDrawable cdDefault = new ColorDrawable(Color.TRANSPARENT);

        states.addState(new int[] {
                android.R.attr.state_pressed
        },
                cdPressed);
        states.addState(new int[] { 
                android.R.attr.state_selected
        },
                cdSelected);
        states.addState(new int[] {},
                cdDefault);

        setBackgroundDrawable(states);
    }
}
  • For each color, create a subclass of ListItem
  • Inside the getView(...) method, inflate your desired layout
  • Don't forget to return the correct type in the getViewType() method
  • Do whatever you want with your color in here
  • Setup the selector with your color

Example:

public class ItemTypeOne extends ListItem {

    public ItemTypeOne(int color) {
        mColor = color;
    }

    @Override
    public int getViewType() {
            // return the type
        return TYPE_WHATEVER_1;
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {

        if(convertView == null) {
            // inflate the layout 
            convertView = inflater.inflate(R.layout.item_type_one, null);
        }    

        // setup the selector
        setupSelectorColor();

        // do other stuff

        return convertView;
    }
}
  • An adapter that supports different item types

Example:

public class ListItemAdapter extends ArrayAdapter<ListItem> {

    public ListItemAdapter(Context context, List<ListItem> objects) {
        super(context, 0, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return getItem(position).getView(LayoutInflater.from(getContext()), convertView);
    }

    @Override
    public int getItemViewType(int position) {
        return getItem(position).getViewType();
    }

    @Override
    public int getViewTypeCount() {
        return ListItem.TYPE_COUNT;
    }
}

Putting it all together:

ArrayList<ListItem> list = new ArrayList<ListItem>();
// fill the list
list.add(new ItemTypeOne(somecolor));
list.add(new ItemTypeTwo(somecolor));
list.add(new ItemTypeOne(somecolor));
list.add(new ItemTypeWhatever(somecolor));

ListView lv = (ListView) v.findViewById(R.id.listView1);

ListItemAdapter a = new ListItemAdapter(Context, list);         
lv.setAdapter(a);

Concerning CustomViews and selectors and their behaviour , I suggest reading this question (and answer): How to implement a CustomView with custom selector states?

I'd say don't overcomplicate this. It can be simple as create an array of int that will contain possible colors, and set them to each item using Random class.

// This goes inside hosting fragment or activity
listview.setOnItemClickListner( new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView parent, View view, int position, long id) {
           if(view.isSelected()){
               view.setSelected(false);
               // also maybe change bg color back to normal?
           }

           else {
                // This one for always a different color
                view.setBackgroundColor(adapter.getColor());

                // This is for foreground color change instead of background
                FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.my_frame_layout);

                final Drawable drawable = new ColorDrawable( /*  your getColor() function  */ );
                frameLayout.setForeground(drawable);

                // This one for alwyas the same color for the row at position given by {@param position}
                view.setBackgroundColor(adapter.getColor(position));
                view.setSelected(true);
           }
      }
});



// All this goes inside your custom listview Adapter
int[] colors = {
      R.colors.red,
      R.colors.blue,
      ...
}

Random random = new Random();

// If each time the selection will bring a different color, use this implementation
public int getColor() {
    return colors[random.nextInt(colors.length)]; 
}

// If each row should have different color, but always the same color for a row then use this one instead
SparseIntArray spa = new SparseIntArray();
public int getColor(int position) {
     if(spa.get(position) == 0) {
         // the color hasnt been created for that row yet
         spa.put(position, colors[random.nextInt(colors.length)];
     }

     return spa.get(position);     
}

**Edit: ** now, if what you want is a foreground selection, then your row should have a FrameLayout container, and you should change it's 'android:foreground' property:

final Drawable drawable = new ColorDrawable( /*  your getColor() function  */ );
frameLayout.setForeground(drawable);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM