Android custom list slow - java

Is there a better way to create a custom list, for my code here seems to make for a less-responsive list. I can't use any of the standard Android lists because I need two ListView's in a ScrollView.
setContentView(R.layout.alertsview);
inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
topRows = new ArrayList<View>();
topLayout = (LinearLayout)findViewById(R.id.topListLayout);
topLayout.removeAllViews();
topRows.clear();
List<Camera> camList = new ArrayList<Camera>(SitesOrchestrator.getInstance().currentSite.cameraList);
for (int i = 0; i < camList.size(); i++)
{
Camera cam = camList.get(i);
View row = inflater.inflate(R.layout.cameraalertrow, null);
TextView name = (TextView)row.findViewById(R.id.cameraNameAlerts);
CheckBox chk = (CheckBox)row.findViewById(R.id.camAlertChk);
name.setText(cam.name);
chk.setChecked(cam.alertsEnabled);
chk.setOnCheckedChangeListener(this);
String index = Integer.toString(i);
chk.setTag(index);
topRows.add(row);
topLayout.addView(row);
}

If you need to lists in the same layout, you should create your own Adapter (derive from base adapter might be good), and, suppose you have two arraylist:
ArrayList<TypeA> typeAList;
ArrayList<TypeB> typeBList;
#Override
int getViewTypeCount(){ return 2; } // means you have two different views from it
#Override
int getItemViewType(int position){
if (position>typeAList.size()) return 1;
return 0;
}
getView(int pos, View convertView, ViewGroup parent){
// Check the pos
if (getItemViewType(pos) == 0){
// Inflate view and bind for type A
}
else{
// Inflate view and bind for type B
}
}
In general, having two list view vertically is not encouraged in Android, but putting both of your content to one list do the trick. I also have a tutorial about this, though it is done in MVVM with Android-Binding.
Moreover, adding Views one by one to ScrollView to mimic the ListView would, of course, be very inefficient. The way Android ListView works (which should different from desktop frameworks) is with recycling views. Suppose you are scrolling up, once the child is scroll out of sight, the listview will recycle the topmost one and place it to the bottom, and the possibly recycled view will pass as convertView in the above getView code. Inflating is considered to be quite expensive process, and multiple child views are memory consuming as well, that's the reason why your code, compare to standard ListView, is very inefficient.

Related

Inflating views in for loop with different identity?

I am inflating layout in view in a for loop and then adding views in Array like:
ArrayList<View> views = new ArrayList();
for (Result datalist : arraylist){
View view = layoutinflater.inflate(R.layout.viewlayout, null);
/*View modification code here....
.............
.............
.............*/
views.add(view);
};
The problem is when i get views from arraylist even by index like views.get(i) it returning only last view. I want to get all views to be able to work on all views.
Here is the code where i am getting views from arrays.
for (View v : views) {
// if (insertPoint != null) {
// if (views != null) {
// if (rel_leftright != null) {
Rect scrollBounds = new Rect();
insertPoint.getHitRect(scrollBounds);
if (v.getLocalVisibleRect(scrollBounds)) {
rel_leftright.setVisibility(GONE);
} else {
rel_leftright.setVisibility(VISIBLE);
}
// }
// }
// }
}
But this code returning only working on last view in array and i need to act on all views.
Please Help me to get out of this. Thanks!!!
First I am missing something:
- Why are you trying to work with the views (/*View modification code here.... ) before you have created all of them? First craete them in the loop and the iterate over views Array and there call the modification code.
- Are you sure you don't want to pass a parent?
View view = layoutinflater.inflate(R.layout.viewlayout, null);
Even if you don't want to be attached you can call:
View view = layoutinflater.inflate(R.layout.viewlayout, parent, false);
And in general it is not possible to fill ArrayList with values and not to find them... Please log the size of datalist, then the size of views. The most probably should match.
And also please note that by the line: /*View modification code here.... not all views are created. You first need to finish the loop.

How to get ArrayList<String> image URLs into ImageView of ListView getView method

#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(resource, null);
}
ImageView imageView;
imageView = (ImageView) convertView.findViewById(R.id.ivGallery);
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
Glide.with(getContext()).load(images).into(imageView);
}
return convertView;
}
// image URLs are stored in string ArrayList . I defined getter and setter for array list but still I don't know how to use get method for showing ArrayList images dynamically in ListView
Extend the class from BaseAdapter
Override getCount() method and return here the total amount of images you need to show.
In the getView load with Glide only ONE url (remove the for loop), the getView method will be called N times to show in the list the "total" amount of images you returned in the getCount method, the position parameter in the getView method will run from 0 to (total - 1).
You should map 1 position to 1 url, maybe you will need to change the way you access the objects that contain the urls.
Okay so first: stop using ListView, use RecyclerView.
Next, you need to override getItemCount(). You can either choose to do this using a for loop:
int count = 0;
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
count += images.size();
}
Or, what is likely preferable, flatten your model object into just what this adapter actually cares about (which is the image URL strings). Something like this:
ArrayList<String> imageUrls = new ArrayList<String>();
for(HospitalModel model : hospitalModelList) {
imageUrls.addAll(model.getImagesList().getImageUrls());
}
Then pass in the imageUrls ArrayList to the Adapter instead. You should only need to flatten this when the model is updated (whether that's initialization or changed). When that occurs, use the notify... methods on the adapter.
After that, it's just a simple Glide.with(getContext()).load(imageUrls.get(position)).into(imageView); inside getView (or onBindViewHolder if you're using a RecyclerView, which I HIGHLY recommend).

ListView OnLongClick strange behaviour

I have a ListView within my project. It has many elements, and it uses a custom adapter, since its populated dynamically from a rails server.
I want to change the content of a ListItem when the item is longpressed. In order to achieve this, I have 2 layouts inside the ListItem, with one visible and one hidden.
The issue is that when I longpress an item, the layout changes (As expected), but other ListItems are also affected, and changed in the same way. This appear to occur once for every 5 items, and I cant figure out why.
This is the LongClickListener I'm using, it is located inside de GetView method on the custom adapter:
View v = convertView;
if (v == null){
LayoutInflater vi =
(LayoutInflater)getActivity().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
final LinearLayout placeInfo =
(LinearLayout) v.findViewById(R.id.list_item_info);
final RelativeLayout placeBrief =
(RelativeLayout)v.findViewById(R.id.list_item_brief);
v.setOnLongClickListener(new OnLongClickListener(){
#Override public boolean onLongClick(View v) {
placeInfo.setVisibility(View.GONE);
placeBrief.setVisibility(View.VISIBLE);
return false;
}});
I would appreciate any help, many thanks in advance.
ListViews recycle Views, so you only have a few views for all of your items. You're directly changing one of these view instances to switch between the info|brief. What you need is to save the status of the info|brief flag for the affected position somewhere else (e.g. a list of positions that should be "briefs" in the adapter). That way when you come back into getView() you can display the right one.

Android Custom Listview

I went through tutorials and searched, but still I can't understand how the,
getView(int position, View convertView, ViewGroup arg2)
method works when extends BaseAdapter to create a custom listView in my android application. Therefore I cant Edit the Custom list view exactly I want.
I need to know when this method invokes and the meanings of the parameters.
If someone can explain the following method its great. Thanks
#Override
public View getView(int position, View convertView, ViewGroup arg2)
{
ViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null)
{
convertView = inflater.inflate(R.layout.listitem_row, null);
holder = new ViewHolder();
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
holder.txtViewDescription = (TextView) convertView.findViewById(R.id.textView2);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.txtViewTitle.setText(title[position]);
holder.txtViewDescription.setText(description[position]);
return convertView;
}
getView() is called when you call setAdapter in your code. After that when you move focus over list or select any item or you call notifyDataSetChanged() , you get call in getView().
Position - The position of the item within the adapter's data set of the item whose view we want.
convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type
The ViewGroup - that this view will eventually be attached to.
getView() : As mentioned in specs getView method displays the data at the specified position. So, when you setAdapter and when you scroll your listView getView method will be called.
The method you copied here is a part of EfficientAdapter to optimize your ListView performance and along with optimization you used ViewHolder pattern.
Copied from Specs : With little more explanation
position :The position of the item within the adapter's data set of the item whose view we want.
convertView: The old view to reuse, if possible. Note: You should check that this
view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
So, in above method when you are doing the following thing you are reusing your convertView.
if (convertView == null){
....
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
And by doing following thing you are avoiding lookup (findViewById), thats what the good thing about ViewHolder pattern
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
parent : The parent that this view will eventually be attached to
Edited
Question : How many times getView is called and how many convertView will be created ?
Answer: Lets take an example of Efficeint Adapter from ApiDemos. If your screen showing 10 Rows, then,
convertView Count : 10 + 1 = 11 (10 Rows what you are seeing on screen, one extra to show scrolling effect). That means statements in if(convertView == null){...} block will be called only 11 times.
getView Count: Initially count will be 10, but when you start scrolling count keep on increasing. getView called every time to update data.
Note: This is only true for getView method mentioned in question.
Here is the description of getView() parameters:
int position - the position of view in the list;
View convertView - IMHO, this is the most difficult parameter for understanding. At the beginning of list's work convertView = null. But when you start to scroll it down, when an item of list (which is instance of View) is hidden, it is stored in memory like convertView. This trick allows you not to create a new item when you scroll your list back, but use convertView stored in memmory. So the first item of list which becomes the convertView is the item at 0 position. Remember, that when you scroll your ListView down (from 0 position to bigger), convertView is situated on the top and on the bottom if you scroll the ListView up.
ViewGroup arg2 - this is your ListView (this class is derived from ViewGroup).
ViewHolder is the pattern, that makes comfortable communication with list's items. You make this object as item's tag and can use them for for indirect interaction with list's item, because it refers on item's fields (View.setTag(holder)).
getView() method is called every time when Android need to draw another one list's item.
Any questions?

Find a view by its type (class) having an Activity instance

package net.example.view;
class StatusBar extends View { ... }
I use it in my layout as following:
<net.example.view.StatusBar .../>
I must find it by it's type(class), I mean something like:
myActivityInstance.findViewsByClass(StatusBar.class) // returns View[]
Is it possible? If not, what is the better approach to find an element without having to use findViewById()?
Thanks.
If you can't use the id and your content view is some kind of ViewGroup you can iterate over the children.
ViewGroup vg = (ViewGroup) activity.getContentView();
for(int i=0; i<vg.getChildCount(); i++) {
View v = vg.getChildAt(i);
if(v instanceof StatusBar) {
// do something interesting.
}
}
Also if you know the specific index you can call getChildAt(index) directly.
I don't think activity has a method that will return an array of Views to you based on class type. You could create and add them to your layout dynamically if you want to get an array of them without using findViewById() something like this:
mLayout = (LinearLayout)findViewById(R.id.mLayout);
StatusBar[] mBars = new StatusBar[10];
for(int i = 0; i < mViews.length; i++){
mBars[i] = new StatusBar(yourActivity.this);
mLayout.addView(mBars[i]);
}
That would give you an array full of StatusBar objects that have all been added to your layout. (You could make the array of type View if you wanted also).
If you are wanting to define all of your views in XML I don't think there is a way that you can avoid using findViewById() in a loop similar to this to get an array full of references to all of them. For this method you'd probably have to create an array of ints that is the same size that contains all of the resIDs of your views
ids[0] = R.id.mStatusBar1;
ids[1] = R.id.mStatusBar2;
etc.

Categories

Resources