2

I have the following situation.

I have a ListView, each item of the ListView is comprised of different widgets (TextViews, ImageViews, etc...) inflated form a Layout in the getView() method of the custom adapter.

Now, I would like to achieve the following:

when a certain event is triggered I want to change the background of a View which is inside the item.

Please how do I do it?


This is the the Item Layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/cardlayout"
android:layout_width="320dp"
android:layout_height="130dp"
android:background="@android:color/transparent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp" >

<FrameLayout
    android:layout_width="320dp"
    android:layout_height="117dp" >

    <View
        android:id="@+id/card"
        android:layout_width="320dp"
        android:layout_height="117dp"
        android:background="@drawable/card_selector" />

</FrameLayout>  
</LinearLayout>

I need to change the background of card

I have tried doing this:

View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);

But no success :-((

Lisa Anne
  • 4,482
  • 17
  • 83
  • 157

6 Answers6

7

When your event is triggered you should just call a notifyDataSetChanged on your adapter so that it will call again getView for all your visible elements.

Your getView method should take into account that some elements may have different background colors (and not forget to set it to normal color if the element doesn't need the changed background, else with recycling you would have many elements with changed background when you scroll)

edit :

I would try something like this :

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null)
    {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.card, parent, false);
    }

    //This part should also be optimised with a ViewHolder
    //because findViewById is a costly operation, but that's not the point of this example
    CardView cardView =(CardView)convertView .findViewById(R.id.card);

    //I suppose your card should be determined by your adapter, not a new one each time
    Card card = getItem(position);

    //here you should check sthg like the position presence in a map or a special state of your card object
    if(mapCardWithSpecialBackground.contains(position))
    {
        card.setBackgroundResource(specialBackground);
    }
    else
    {
        card.setBackgroundResource(normalBackground);
    }
    cardView.setCard(card);

    return convertView;
}

And on the special event i would add the position of the item into the map and call notifyDataSetChanged.

Oleksii K.
  • 5,359
  • 6
  • 44
  • 72
Xavier Falempin
  • 1,186
  • 7
  • 19
  • Thanks Xavier, same as in my comment to Mahmoud's answer, I cannot call `notifyDataSetChanged()` :-((( – Lisa Anne Oct 14 '14 at 14:38
  • This should be the accepted answer. Xavier, you should add the comments you made @ Mahmouds answer here because it's good information. – prettyvoid Oct 25 '14 at 21:46
2

Use the onitemclicklistener which has method onclicksomething..that takes four or five parameters. (View parent, View view, int position, int id). Use the view parameter to customize your background.

Update Here's some of my code, If you don't understand I recommend to read about recycling and ViewHolder pattern.

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
    {
            ViewHolder viewHolder;
            // If convertView isn't a recycled view, create a new.
            if(convertView == null){
                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.row_gallery_frame, parent, false);

                viewHolder = new ViewHolder();
                // Here you must be able to find your Widget inside convertView and set a listener to it I guess?
                viewHolder.nameHolder = (TextView) convertView.findViewById(R.id.nameTv);
                // Set a reference to newly inflated view
                convertView.setTag(viewHolder);
            }
            // If it is, then get the ViewHolder by tag
            else{
                viewHolder = (ViewHolder)convertView.getTag();
            }
            // Set the data
            GalleryFrame galleryFrame = galleryFrameArrayList.get(position);

            viewHolder.nameHolder.setText(galleryFrame.getName());

            return convertView;
        }
    }
    // Viewholder pattern which holds all widgets used
    public static class ViewHolder{
        public TextView nameHolder;
    }
Rovdjuret
  • 1,458
  • 3
  • 19
  • 40
  • Thanks Rodv, the problem is that I need a Wideget which is INSIDE the View... :(( – Lisa Anne Oct 14 '14 at 14:39
  • But use mysomething = (someview)view.findviewbyid(R.id.widgetid); mysomething.backgroundcolor... – Rovdjuret Oct 14 '14 at 14:41
  • Rody, thanks Rody, that won't work, you get null if you call `findviewbyid` that way – Lisa Anne Oct 14 '14 at 14:57
  • Could you provide more info on how your custom adapter looks like in the question? I've done this before and it shouldn't be hard to solve. – Rovdjuret Oct 14 '14 at 14:59
  • @LisaAnne Your getting null because your are creating new view each time you scroll down and show more items. Because of this, it wont know about those previous created. This may cause laggy scrolling if you maybe have noticed. In other words, you have to recycle your views with a ViewHolder pattern. Ill update my answer with a example. Hold on :) – Rovdjuret Oct 14 '14 at 16:38
2

I assume you have a model object that you use to "draw" the list item , and for example the background color is determined based on a boolean or something.

All you need to do, is change the value on which you base your decision which background color should that TextView have.

Your getView() method should have code like that

if (myModelObj.isBrown()) {
    myTextView.setBackgroundResource(R.drawable.brown_bg);
else
    myTextView.setBackgroundResource(R.drawable.not_brown_bg);

All you should do when ur event is triggered, is set the value of the brown boolean in your model and call notifyDataSetChanged() on your adapter

EDIT

If for some reason you don't wanna call nofitfyDataSetChanged(), althought it won't move the scroll position of your list and with the right recyclying it won't cause bad performance

You can find the View object that represent the list item you want to edit-if it's visisble-, and simply change the background in it, without refreshing the list at all.

int wantedPosition = 10; // Whatever position you're looking for
int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount();
int wantedChild = wantedPosition - firstPosition
if (wantedChild < 0 || wantedChild >= listView.getChildCount()) {
    // Wanted item isn't displayed
    return;
}
View wantedView = listView.getChildAt(wantedChild);

then use wantedView to edit your background

This answer can be found here

Community
  • 1
  • 1
elmorabea
  • 3,243
  • 1
  • 14
  • 20
  • Thanks Mahmoud but I cannot call `notifyDataSetChanged()` because I cannot afford the entire ListView to be redrawn, because I need the ListView to stay in the same position as selected by the user :-(( – Lisa Anne Oct 14 '14 at 14:37
  • notifyDataSetChanged (and not invalidated) won't make your list scroll – Xavier Falempin Oct 14 '14 at 14:50
  • Xaview you are right, but causes an horrible blinking of the whole ListView :-(( – Lisa Anne Oct 14 '14 at 15:01
  • It's because you don't take advantage of recycling, you always inflate your view even if the convertView provided is not null. You are supposed to not inflate it again, but just change what is specific to your item – Xavier Falempin Oct 14 '14 at 15:58
1

try this one:

View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
card.invalidate();
v.invalidate();

those function force your views to redraw itself and they will render again. look at invalidate()

mmlooloo
  • 18,937
  • 5
  • 45
  • 64
1

What I normally do is this:

public static class EventDetailsRenderer {

    private TextView title;

    private TextView description;

    private Event item;

    public EventDetailsRenderer(View view) {
        extractFromView(view);
    }

    private final void extractFromView(View view) {
        title = (TextView) view.findViewById(R.id.EventTitle);
        description = (TextView) view.findViewById(R.id.Description);
    }

    public final void render() {
            render(item);
    }

    public final void render(Event item) {
        this.item= item;
        title.setText(item.getTitle());
        description.setText(item.getDescription());
    }
}

private class EventsAdapter
        extends ArrayAdapter<Event> {

    public EventsAdapter(Context context) {
        super(context, R.layout.list_node__event_details, 0);
    }

    public void addAllItems(Event... services) {
        for (int i = 0; i < services.length; i++) {
            add(services[i]);
        }
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Event event = getItem(position);
        EventDetailsRenderer eventRenderer;

        if (convertView != null && convertView.getTag() != null) {
            eventRenderer = (EventDetailsRenderer) convertView.getTag();
        } else {
            convertView = getActivity().getLayoutInflater().inflate(R.layout.list_node__event_details, null);
            eventRenderer = new EventDetailsRenderer(convertView);
            convertView.setTag(eventRenderer);
        }

        eventRenderer.render(event);
        return convertView;
    }
}

NOTE: that this example might not compile I pasted it from some code I have and deleted some lines to show an example but the logic it the same.

And then when you want to render it, just get the children from the list, iterate over them, check if the renderer contains the card you want to flip and call its render method... then you render a specific item in the list without effecting the rest of the items.

Let me know if this works...

Adam.

TacB0sS
  • 10,106
  • 12
  • 75
  • 118
1

User EasyListViewAdapters library https://github.com/birajpatel/EasyListViewAdapters

Features

  1. Easier than implementing your own Adapter (ie handling BaseAdaper#getView).Very Easier to provide multi-row support.
  2. Library takes care of recycling all views, that ensures performance & helps your list view scroll smoothly.
  3. Cleaner code. By keeping different RowViewSetter classes for different row-types makes your code easy to manage & easy to reuse.
  4. No data browsing, Library takes care of browsing data through data-structure when View is being drawn or event occurs so that Users does not have to look for their data to take actions.
  5. Just by passing correct row-types library will Auto-map your data-types to row-types to render views. Row views can be created by using XML or Java (doesn't restrict to XML-Only Approach).
  6. Load More callbacks can be registered to implement paginatation support to your list.
  7. Handling children viewclicks, you can also register for Children(present inside your rows) view click events.
  8. All these Views are registered with single OnClickListner so that this mechanism is very memory efficient when click event occurs users you gets clickedChildView, rowData,int eventId as callback params.

enter image description hereenter image description hereenter image description here

AZ_
  • 21,688
  • 25
  • 143
  • 191