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
Related
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
}
I've got a ListView in my application that's rendered in a ListFragment's onActivityCreated()using setListAdapter(). Passed in to this setListAdapter()are my implementation of ArrayAdapter. At some times this adapter can be empty, and that's fine, but at those moments I would like to show a message in the list telling that there are no items, instead of just an empty view. However I don't really know how to achieve this, as for I have researched most people to show lists in a ListActivityand by doing that you can setEmptyView() but this doesn't seem doable when using a ListFragment. Another way of doing this was to change view in the ListFragment if the ArrayAdapter has no item's in it, then change view to another showing the message, but this seems to me a bit hacky.
Whats really the proper way of doing what I want to achieve?
Tried to setEmptyView() to my ListView but that didn't work either, see code on how views are inflated in my ListFragment:
public class MyFragment extends ListFragment {
#SuppressLint("InflateParams")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ListView listView = (ListView) inflater.inflate(R.layout.my_list, null);
listView.setEmptyView(inflater.inflate(R.layout.list_items_missing, null));
return listView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MyItemArrayAdapter adapter = new MyItemArrayAdapter(getActivity());
// Populate adapter with items...
setListAdapter(adapter);
}
}
Shouldn't this result in the empty view beeing shown if no items exists in my adapter?
1) Try putting backgroundImage for ListView inside xml. If no item, show backgroundImage, if there is item, put background color.
2) You can also do what as #kgandroid suggested and here, setEmptyView() which you set custom view.
Example:
Another:
You can customize your adapter to show the layout R.layout.list_items_missing when (item == null) instead of inflating the normal custom list item layout that you implement.
Something like this:
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder; //asuming you have this in your custom adapter
if (convertView == null) {
holder = new ViewHolder();
if(MyItemArrayList.get(position)!=null)
{
convertView = this.inflater.inflate(R.layout.layout_list_item,
parent, false);
else
{
convertView = this.inflater.inflate(R.layout.list_items_missing,
parent, false);
}
//the rest of the adapter logic
}
I've searched through a lot of other answer for the same problem, but didn't found any solution that works for me. The problem, as the title says, is that the getView method from my custom adapter doesn't get called.
Here's the code (first the fragment):
public class CategoryListFragment extends ListFragment
implements NewCategoryDialogListener {
private GestoreAttivitaDbHelper mDbHelper;
private CategoryListAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mAdapter = new CategoryListAdapter(getActivity());
setListAdapter(mAdapter);
CategoryLoader categoryLoader = new CategoryLoader();
if (mDbHelper == null) {
mDbHelper = new GestoreAttivitaDbHelper(getActivity().getApplicationContext());
}
SQLiteDatabase db = mDbHelper.getReadableDatabase();
mAdapter.addAll(categoryLoader.getAllCategories(db));
mAdapter.notifyDataSetChanged();
mAdapter.getCount();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.category_list, container);
}
Here's the adapter:
public class CategoryListAdapter extends ArrayAdapter<CategoryElement> {
private LayoutInflater mInflater;
public CategoryListAdapter(Context ctx) {
super(ctx, R.layout.category_element);
mInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
Log.d("Adapter", "Restituisco la view per l'elemento");
if (convertView == null) {
view = mInflater.inflate(R.layout.category_element, null);
} else {
view = convertView;
}
//((TextView) view.findViewById(R.id.category_element_text)).setText(getItem(position).getName());
TextView textView = (TextView) view.findViewById(R.id.category_element_text);
textView.setText(getItem(position).getName());
return view;
}
}
And here's my two layout files:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/category_list"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
and:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/category_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="#+id/category_element_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
</LinearLayout>
I thought that setting the adapter in the onCreate could be a problem, since it is called before onCreateView, and at that time the fragment isn't already associated with the ListView. So I moved the code from the onCreate to the onStart method, but nothing changed.
Also, the getCount() correctly returns me 6, the precise number of element red from the database.
Any help would be really appreciated!!
Thanks.
Edit:
Solved!
Problem was in the activity, I had the following code:
fragmentTransaction.add(0, categoryListFragment);
that I changed in
fragmentTransaction.add(R.id.activity_main, categoryListFragment);
Without specifying the View id to which the fragment should be attached it never draws it!
In addition, I had to change from this
view = mInflater.inflate(R.layout.category_element, parent);
to this:
view = mInflater.inflate(R.layout.category_element, null);
in the getView method.
PS. I'm editing this cause I can't answer my own question until 8 hours have passed..
I think in your R.layout.category_list file you need to give the ListView the following attribute:
android:id="#android:id/list"
ListFragment (and ListActivity) look for this id to find the ListView when you call methods like setListAdapter().
Also, if all you want is a ListView, you don't have to supply a layout file. Simply do not override onCreateView() and the system will provide a ListView for you automatically. Only if you want a custom layout do you need to inflate one, and if you do, the ListView should have the id stated above.
I have the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="#+id/ListView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:textColor="#android:color/white"
android:dividerHeight="1px"
android:listSelector="#drawable/highlight_sel"
/>
</LinearLayout>
And the code:
private ListView lv1;
private String lv_arr[]={"Item 1","Item 2","Item 3","Item 4"};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.newsmenu);
lv1=(ListView)findViewById(R.id.ListView01);
// By using setAdpater method in listview we an add string array in list.
lv1.setAdapter(
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
lv_arr));
}
I want the text color of Item 2 (or 1 or 3 or 4) to appear dynamically as red (denoting a new item) or white (default). Is there a way to do this?
I already have a selector present, which is why I used ListView. I've search the Internet and this site, and I have not seen this question broached.
So is it possible?
Yes everything is possible. you need to write your own adapter implementation basically overriding the getView Method in the adapter. search google and stack you will find many tutorials on how to write an adapter.
Writing a special adapter to override getView in simple adapter is the way to change the text color alternating on the lines of your choice in a listview. I took the example which has been repeated many times on this website and added a way to change the text color. position mod length to select the color position can be replaced with any scheme you like. The text view "business" can be the first line of your layout like mine--or use the android.R.id.text1.
public class SpecialAdapter extends SimpleAdapter {
private int[] colors = new int[] { 0x30FF0000, 0x300000FF };
public SpecialAdapter(Context context, List<HashMap<String, String>> items, int resource, String[] from, int[] to) {
super(context, items, resource, from, to);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
int colorPos = position % colors.length;
//view.setBackgroundColor(colors[colorPos]); //old example
TextView tv1 = (TextView)view.findViewById(R.id.business); //new
tv1.setTextColor(colors[colorPos]); //new
return view;
}
}
Just use SpecialAdapter instead of SimpleAdapter in your app.
Here's an example of a getView method. Note that it's using a viewholder for efficiency. If you want to know more about that, let me know.
public View getView(int position, View convertView, ViewGroup parent) {
tempDeal = exampleBoxArrayList.get(position);
ViewHolder holder;
if (convertView == null) {
convertView = inflator.inflate(R.layout.list_item_example_box, null);
holder = new ViewHolder();
holder.divider = (RelativeLayout) convertView.findViewById(R.id.example_box_divider);
holder.merchantName = (TextView) convertView.findViewById(R.id.example_box_merchant_name);
holder.expireDate = (TextView) convertView.findViewById(R.id.example_box_expire_date);
holder.description = (TextView) convertView.findViewById(R.id.example_box_description);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (tempDeal.isDivider()) {
holder.divider.setVisibility(View.VISIBLE);
} else {
holder.divider.setVisibility(View.GONE);
}
holder.merchantName.setText(tempDeal.getMerchantName());
holder.expireDate.setText(tempDeal.getExpiryDateString());
holder.description.setText(tempDeal.getPriceOption().getDescription());
return convertView;
}
As you can see, I call the isDivider() method on my custom object (this method looks at a boolean set on data load). This method is used to turn the visibility of part of the layout on or off.
Alternatively, you could load a completely new layout based on this same concept.
I have an activity with multiple list views that are continuously receiving new values form a socket thread, another thread parses the data and updates the array adapters, then the ui thread calls notifyDataSetChanged() to cause the list to refresh.
My issue is that im refreshing all the list a couple of time a second, this causes the UI to be very laggy when some animations need to happen.
I was wondering what the best way is to update multiple lists with multiple value changes every second?
Thanks,
Totem.
I would definately follow the guidelines they gave at Google IO this year.
Google IO listview Video
You should use Cursors (if required Content Providers) and ListActivity. The UI automatically updates as soon as there are changes and in case of null data sets, the list automatically displays a relevant view.
Following examples solves it using content providers:
main.xml:
<ListView android:id="#id/android:list" android:layout_width="fill_parent"
android:layout_height="wrap_content"></ListView>
<TextView android:id="#id/android:empty" android:layout_width="fill_parent"
android:gravity="center" android:layout_height="wrap_content"
android:text="No data, please refresh!" />
Notice the android:list and android:empty tags. These are required by the list activity.
In onCreate() method:
mCursor = getContentResolver().query(SOME_URI,
null, null, null, null);
ListView mListView = (ListView) findViewById(android.R.id.list);
mListView.setAdapter(new CustomCusorAdapter(getApplicationContext(),
mCursor));
You can use a SimpleCursorAdapter if your views are straight-forward. I created by own adapter because of the complex views:
private class CustomCusorAdapter extends CursorAdapter {
public CustomCusorAdapter(Context context, Cursor c) {
super(context, c);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
Holder holder = (Holder) view.getTag();
holder.tv.setText(cursor.getString(cursor
.getColumnIndex(COL1_NAME)));
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = getLayoutInflater().inflate(
R.layout.layout_xml, null);
Holder holder = new Holder();
holder.tv = (TextView) v
.findViewById(R.id.tv);
holder.cb= (CheckBox) v
.findViewById(R.id.cb);
v.setTag(holder);
return v;
}
}
private class Holder {
TextView tv;
CheckBox cb;
}
Yes, that video is very helpful. One of the biggest take aways is that you should recycle the convertView passed into your list adapters getView method:
public View getView(int position, View convertView, ViewGroup parent){
if (convertView == null) {
convertView = mInflator.inflate(resource, parent, false);
}
//do any view bindings for the row
return convertView;
}
The other helpful bit was to use a ViewHolder class for the view recycling. It's at ~10:30 into the vid.