I have a normal setup for a ListView and Custom ArrayAdapter.
and i'm stipulating if something equals to something then change color of that item.
at the first load, the condition works for correct items that i change thier color (if condition).
The issue begins to appear when i scroll the ListView up and down continually, the color changing for wrong items randomly and reverts back to original as i keep scrolling. even the correct ones randomly changing. till i get all the items on the list set with that color!
Let's say only one item has (true) value and the rest is (false) then i condition if true, change color. but when i scroll (not at first load) other items gets that color even they're false.
but the data i set with disabledTextView.setText("correct item"); does not change it keeps as it's correct, which is good.
MyCustomAdapter.java
public class MyCustomAdapter extends ArrayAdapter<HashMap<String, String>> {
private Context context;
private ArrayList<HashMap<String, String>> myArray;
public MyCustomAdapter(Context context, ArrayList<HashMap<String, String>> theArray) {
super(context, R.layout.my_single_list_item, theArray);
this.context = context;
this.myArray = theArray;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.my_single_list_item, parent, false);
}
HashMap<String, String> arrayItem = myArray.get(position);
boolean disabled = Boolean.valueOf(arrayItem.get("disabled"));
TextView disabledTextView = convertView.findViewById(R.id.disabledTextView);
disabledTextView.setText(String.valueOf(disabled));
if(disabled) {
disabledTextView.setTextColor(getContext().getResources().getColor(R.color.design_default_color_error));
}
return convertView;
}
}
my_single_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/disabledTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#android:color/black" />
</LinearLayout>
What causes this problem?
Update (Solved) : but more information still needed!
after getting rid of using convertView and replaced it with customView with new LayoutInflater :
public View getView(int position, View convertView, ViewGroup parent) {
View customView = LayoutInflater.from(getContext()).inflate(R.layout.my_single_list_item, parent, false);
}
TextView hotspotUserDisabled = customView.findViewById(R.id.hotspotUserDisabled);
//.. etc
The problem is solved. i'm still too confused with that, should i only use new View inflate only if View convertView is null?
because the IDE shows a hint :
When implementing a view Adapter, you should avoid unconditionally inflating a new layout;
if an available item is passed in for reuse, you should try to use that one instead.
This helps make for example ListView scrolling much smoother.
What if i need to fix this and still use the View that is passed to keep scrolling smoother?
Thanks!
You need to define else case in getView method
if(disabled) {
disabledTextView.setTextColor(getContext().
getResources().getColor(R.color.design_default_color_error));
}else{
// add code here
}
Related
I recently encountered an issue where the animation of an indeterminate ProgressBar used inside of a ListView row became choppy. In a nutshell, I have a ListView where each row contains a ProgressBar. The animations look great, until I scroll; from then on, at least one of the ProgressBar will have a choppy animation.
Does anyone know how to fix this problem?
View for the ListView row
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:indeterminate="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Simple custom ArrayAdapter
public class MyAdapter extends ArrayAdapter {
List list;
public MyAdapter(Context context, List objects) {
super(context, 0, objects);
list = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = ((LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.list_row, parent, false);
}
return convertView;
}
}
OnCreate() method for the sample Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
ListView listView = (ListView)findViewById(R.id.list_view);
ArrayList<Integer> data = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,0,11,12,13,14,15,16,17));
MyAdapter adapter = new MyAdapter(this, data);
listView.setAdapter(adapter);
}
Bug logged (contains sample project): https://code.google.com/p/android/issues/detail?id=145569&thanks=145569&ts=1423673226
Try implementing the view holder pattern and check the performance.
Create a static ViewHolder class with a progress bar.
static class ViewHolder {
ProgressBar progress;
}
and in your getView() you get find the view holder from the view only when the convertView is null, otherwise take it from the tag holding the viewHolder. This way you are inflating a new view only when the convertView is null, otherwise you are using the views stored in your viewholder tag.
A simple tutorial can be found here.
it is happening for the first time only, if you close and reopen the app, you will not notice it.
Did you check in older versions like kitkat?
And dont create LayoutInflater in the getView(), create once in the constructor and use it in the constuctor
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 :-((
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.
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;
}
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
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()
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.
User EasyListViewAdapters library https://github.com/birajpatel/EasyListViewAdapters
Features
Easier than implementing your own Adapter (ie handling
BaseAdaper#getView).Very Easier to provide multi-row support.
Library takes care of recycling all views, that ensures performance
& helps your list view scroll smoothly.
Cleaner code. By keeping different RowViewSetter classes for
different row-types makes your code easy to manage & easy to reuse.
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.
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).
Load More callbacks can be registered to implement paginatation
support to your list.
Handling children viewclicks, you can also register for
Children(present inside your rows) view click events.
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.
I am trying to get have the lower part of list view slide down, by hiding an unhiding linear layout in list_item. The problem is the view seems to get reused in LayoutAdapter so that the change does not just effect the view I intended to apply it to. Instead it shows up wherever the view is reused. How can I restrict the drop down to just the view on which I requested the dropdown? By drop down I mean unhide the linear layout.
There will always be as many Views in the List as can be seen by the user. When the user scrolls, the views that go out of view get reused to show the new list data the user scrolled to. You need to reset the list item's state when they are redrawn.
Add a boolean variable 'expanded' to the object that stores the list data. (The objects you add to the ArrayAdapter). Set expanded = true when the user expands the LinearLayout in the listItem.
public class MyListItem
{
public boolean expanded = false;
// data you are trying to display to the user goes here
// ...
}
Then do this in the list adapter's getView method
public class MyListAdapter extends ArrayAdapter<MyListItem>
{
public MyListAdapter (Context context, ArrayList<AudioPlaylist> objects)
{
super(context, R.layout.list_item, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LinearLayout rowLayout;
MyListItem item = this.getItem(position);
if (convertView == null)
{
rowLayout = (LinearLayout) LayoutInflater.from(this.getContext()).inflate(R.layout.list_item, parent, false);
}
else
{
rowLayout = (LinearLayout) convertView;
}
//set the textviews, etc that you need to display the data with
//...
LinearLayout expanded = rowLayout.findViewById(R.id.expanded_area_id);
if (item.expanded)
{
//show the expanded area
expanded.setVisibility(View.VISIBLE);
}
else
{
//hide the area
expanded.setVisibility(View.GONE);
}
return rowLayout;
}
}
Make sure your list_item.xml has a LinearLayout wrapping the whole thing otherwise you will get an cast exception.
Hope that helps...
I have a ListView that's populated via an ArrayAdapter. Within the adapter, i set the view background color dependent on a conditional. It works, but while scrolling the remaining rows adopt this color. Here's some code:
class DateAdapter extends ArrayAdapter<DateVO> {
private ArrayList<DateVO> items;
public ViewGroup listViewItem;
//constructor
public DateAdapter(Context context, int textViewResourceId, ArrayList<DateVO> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
if (view == null) {
LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.row, null);
}
final DateVO dateItem = items.get(position);
if (dateItem != null) {
//is this my issue here? does position change while scrolling?
if(items.get(position).getField().equals("home")){
view.setBackgroundResource(R.drawable.list_bg_home);
}
...
}
}catch (Exception e) {
Log.i(ArrayAdapter.class.toString(), e.getMessage());
}
return view;
}
}
That is the default behavior of a ListView. It can be overridden by setting the cacheColorHint to transparent.
Just add,
android:cacheColorHint="#00000000"
in your xml file.
For more details you can go through the ListView Backgrounds article.
Here is an excerpt:
To fix this issue, all you have to do is either disable the cache color hint optimization, if you use a non-solid color background, or set the hint to the appropriate solid color value. You can do this from code (see setCacheColorHint(int)) or preferably from XML, by using the android:cacheColorHint attribute. To disable the optimization, simply use the transparent color #00000000. The following screenshot shows a list with android:cacheColorHint="#00000000" set in the XML layout file
EDIT : The view passed as convertView is essentially a view which is a part of the list view, but isn't visible anymore (due to scrolling). So it is actually a view you had created, and might be a view for which you had set the custom background. To solve this just make sure that you reset the background if the condition is not satisfied. Something like this :
if(condition_satisfied) {
//set custom background for view
}
else {
//set default background for view
convertView.setBackgroundResource(android.R.drawable.list_selector_background);
}
Essentially if your condition is not satisfied you will have to undo whatever customisations you are doing when your condition is satisfied, because you might have received an old customised view as convertView.
That should solve your problem.
This is the first time I need to use the ArrayAdapter<T> to show a multi-item Row-Layout. After a lot of successful work with different adapters this one is driving me crazy.
getView(..., position, ...) always returns 0-7[EDIT] so I never see elements in my ArrayList that are on position >= 7[/EDIT]. I know, this is the visible position, but how do I select the correct object in the ArrayList?
EDIT: Currently I only get the first 8 elements out of my array because position only comes in from 0-7 - even on a 50 element ArrayList. I don't see a way to position within the ArrayList without a "real" position.
The docs say the following - but I don't get it. Did somebody successfully implement an ArrayAdapter<T> with a complex layout? What do the doc mean and how should I implement it?
If you want to use a more complex
layout, use the constructors that also
takes a field id. That field id should
reference a TextView in the larger
layout resource. However the TextView
is referenced, it will be filled with
the toString() of each object in the
array. You can add lists or arrays of
custom objects. Override the
toString() method of your objects to
determine what text will be displayed
for the item in the list. To use
something other than TextViews for the
array display, for instance,
ImageViews, or to have some of data
besides toString() results fill the
views, override getView(int, View,
ViewGroup) to return the type of view
you want.
Many thanks in advance
hjw
Here's the code so far:
public class HappyAdapter extends ArrayAdapter<Happy> {
static class ViewHolder {
private ImageView imageView;
private TextView textViewBottom;
private TextView textViewTop;
}
private ArrayList<Happy> arrayListHappy;
private DrawableCache drawableCache = DrawableCache.getInstance();
private int layout;
#Override
public int getCount() {
return arrayListHappy.size();
}
#Override
public View getView(int position, View contentView, ViewGroup viewGroup) {
// position always 0-7
View view = null;
ViewHolder viewHolder = null;
if (contentView == null) {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater.inflate(layout, null);
if (view != null) {
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view.findViewById(R.id.happyactivity_row_image_left);
viewHolder.textViewBottom = (TextView) view.findViewById(R.id.happyactivity_row_text_bottom);
viewHolder.textViewTop = (TextView) view.findViewById(R.id.happyactivity_row_text_top);
view.setTag(viewHolder);
}
} else {
view = contentView;
viewHolder = (ViewHolder) contentView.getTag();
}
if (viewHolder != null) {
Happy happy = arrayListHappy.get(position);
if (happy != null) {
viewHolder.imageView.setUrl(happy.getImageThumbnail());
drawableCache.fetchDrawable(happy.getImageThumbnail(), viewHolder.imageView);
viewHolder.textViewBottom.setText(String.valueOf(position));
viewHolder.textViewTop.setText(String.valueOf(viewHolder.position));
}
}
return view;
}
public HappyAdapter(Context context, int layout, ArrayList<Happy> arrayListHappy) {
super(context, layout, arrayListHappy);
this.arrayListHappy = arrayListHappy;
this.layout = layout;
}
}
This is part of the Row-Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="horizontal" >
<ImageView
android:id="#+id/happyactivity_row_image_left"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="fill_parent" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_top"
android:layout_weight="1" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_bottom"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
I can't add comments yet so have to answer. As others have noted it isn't really clear what's not working. However, you are only seeing positions 0-7 being inflated as this is the only portion of the list that is currently visible, as you noted yourself. Other rows (with higher position numbers) won't be inflated until you scroll down the list.
Although it uses the BaseAdapter class, rather than ArrayAdapter that you are using, you could look at List14.java in the ApiDemos sample code (ApiDemos/src/com/example/android/apis/view/List14.java) as the principle is the same. When using ArrayAdapter though, you don't need to override all the methods that this sample code does (just getView).