I'm currently having problems with a load of thumbnails in my android application. The thumbnails are displayed in list views and I'm wondering if there is a way for me to know which list items are currently in use and stored for faster scrolling, so that I can recycle unused thumbnails if I get a out of memory response from the BitmapFactory.
To be more clear when loading an image into an image view this is really simple but is there a way to observed which items are currently in use or even better a method that is called in a list view when an item is thrown out?
Thanks in advance.
The standard ListView dynamically creates the views of only those items that will be visible. Even if you have more items in your list than fit on the screen there will be no more views than needed to show the items on the screen.
To list those items that are currently visible on the screen use ListView.getChildAt() and iterate from 0 to ListView.getChildCount(). I.E. do something like this:
for (int i = 0; i < listView.getChildCount(); i += 1) {
// the view itself
View itemView = listView.getChildAt(i);
// the data it is representing (I used String just as an example)
String itemData = (String) listView.getItemAtPosition(
i + listView.getFirstVisiblePosition());
// do whatever you need to do with it
}
I don't know an easy way to get notified for views that are being recycled. Perhaps you can extend the ListView class and override some methods. Perhaps someone already built a component that has what you need.
Use ViewHolder pattern or RecyclerView
Related
So we are supposed to use the recycler view when we want to show a large list of elements.
The benefit of that is that views are re-used so we don't inflate each view in the list and keep them in memory and just keep in memory those that are displayed in the screen recycling the rest.
But what if the recycled view is itself a "mini" list?
I.e. for each item recycled we change its structure by removing all child views and adding new child views?
Is that defeating the whole idea of using a recycled list?
Example in question (itemInRecycler is a vertical LinearLayout that is passed to the recycler view holder):
itemInRecycler.removeAllViews();
for(element: elements) {
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
Is that defeating the whole idea of using a recycled list?
It depends on how much of the whole itemView the sub-list is. If the sub-list is the only thing you're displaying, then yes, you're defeating a large portion of the performance gains. You're still inflating views every time you bind the ViewHolder, and that's one of the things you try to avoid when using RecyclerView.
It also depends on how large the sub-list is. If it's only three elements at most, then the cost is diminished. If the sublist is hundreds of items, then the cost is large.
One option is to use a sub-RecyclerView instead of a LinearLayout, and to connect each sub-RecyclerView to the same shared RecycledViewPool. This way you get all the benefits of RecyclerView vs a scrollable LinearLayout, but even better because each sub-RecyclerView can take ViewHolders from the others.
If that's a little too heavy-handed for you, you could potentially just optimize the code you've already written. Instead of clearing the list each time and then re-inflating the correct number of views, you could re-use existing views (and only create new ones if there aren't enough) and remove the extra views if there are too many.
int i = 0;
// reuse existing views
for (; i < elements.size() && i < itemInRecycler.getChildCount(); i++) {
Element element = elements.get(i);
CustomView view = (CustomView) itemInRecycler.getChildAt(i);
view.setDisplayData(element);
}
// create new views if there weren't enough to reuse
for (; i < elements.size(); i++) {
Element element = elements.get(i);
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
// remove any extras after we've reused everything
int viewsToRemove = itemInRecycler.getChildCount() - i;
if (viewsToRemove > 0) {
itemInRecycler.removeViews(i, viewsToRemove);
}
In a world where the elements collection always has between five and seven elements, say, this approach will make sure that you're always re-using the first five CustomViews, and then only inflating or removing 0-2 extra CustomViews.
What will happen if we keep on loading thousands of items in any type list in Android? Will Android be able to recycle it correctly?
Short Answer
In your case, there Would not be any problem in recycling of list view or recycler view.
Long Answer
The list view or recycler view would just load and show the amount of data which are visible on the UI. So the size of the items in it is not any problem.
BUT
there are some cases which would make problem.
for example if you put a recycler view inside a Scroll view!
In such cases all of the items are loaded and there is no GC. That's where you would have big problems with lots of data.
List is an Interface, not a class, so there are ArrayLists and LinkedLists and several other types, but ArrayLists for example can handle up to Integer.MAX_VALUE elements. I've never had a problem with an ArrayList and garbage collection at least and thousands of items is far below the limits of that class.
I have a listing of some item and I am using Glide to load an image onto an ImageView. The problem is, if a particular item, scroll-enter the screen, that is the only time I saw image is being drawn using Glide. This is noticeable when I put crossfade when the image has finished loading.
I know from the start the number of items needed and couple of other items like header and footer. I am thinking of just making a very long view to handle everything and dynamically add those known items at onCreate and just use ScrollView which I have done. I currently implemented a simpler way using a RecyclerView but I having trouble regarding the timing of onBindView call.
What I want is to load first 5 items before they become visible into the screen. This I hope will trigger the method where I bind the image onto the ImageView with Glide before it is fully visible into the screen.
I followed this tutorial and its not working for me. Here is my current setup:
mComicListingAdapter = new ComicListingsAdapter(getContext(), Constants.CHAPTERS, (ComicListingsAdapter.Callback) getActivity());
mRecycleViewHomeScreen.setAdapter(mComicListingAdapter);
mRecycleViewHomeScreen.setHasFixedSize(true);
mRecycleViewHomeScreen.setItemViewCacheSize(42);
mRecycleViewHomeScreen.setLayoutManager(pcll);
Is this possible in RecyclerView? Or maybe I should try dynamically creating ImageView and appending them to a parent View as I have done so. It looks great but there is a lot of things to do in my case using this method. I am just curious if I can direct RecyclerView to load n-amount of items in advance.
Thanks!
I've found a blog post where author suggest to extend LinearLayoutManager and override getExtraLayoutSpace().
And it worked. More items were preloaded depending on the number of pixels returned in getExtraLayoutSpace().
P.S. I've not checked yet how that affects performance, especially on pre-lollipop devices.
Sample:
val customLayoutManager = CustomLayoutManager(this)
recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
layoutManager = customLayoutManager
... //
}
class CustomLayoutManager(context: Context?) : LinearLayoutManager(context) {
override fun getExtraLayoutSpace(state: RecyclerView.State?) = 2000
}
Views are created dynamically at runtime and you can only access view items at onBindViewHolder method of adapter. You want to load images before setting adapter to recycler view. Firstly the getItemCount method is being called which will look for number of items and then onCreateViewHolder method is called to draw a row into your RecyclerView.
So this is not possible in RecyclerView to access items before setting any Adapter. You need to achieve this in some other way by dynamically creating an ImageView, for example.
I'm dynamically adding Views to my items in a RecyclerView. These added Views should only be related to the item which they're added to, but I'm having a problem when I scroll. It seems the View is recycled and a new item is loaded, but those previously added views are still there, only now on the wrong item.
I'm assuming that it's just because the ViewHolder is being reused, so the added items show up again with a new item, when loaded.
How would one go about solving this?
This was an old question of mine. A bounty was placed on it, hence the surge of popularity and the multiple new and irrelevant answers.
As stated in both my comment to this answer and #CQM's comment below my original question, the answer is to override the onViewRecycled() method and perform any needed operations there. This method is called when a view is recycled, and any cleanup operations can be done here.
Documentation on this method can be found here.
In my case, it was a matter of deleting the invisible TextView's attached to the view. The text itself had been deleted, but the view remained. If many invisible TextView's accumulate on the view and aren't properly recycled when scrolling, the scroll will begin to lag.
You need to track what views have been added based on the backing data. I would probably add any necessary extra views in onBindViewHolder(), and remove any that might be present in onViewRecycled(). Then when you want to make one appear dynamically, change whatever variable you have tracking whether it should be visible, and call notifyItemChanged().
Based on this:
but those previously added Views are still there, but now on the wrong item.
Basically, as per the RecyclerView documentation, You have to reset the views everytime inside the onBindViewHolder() method,
so let say, you have a method that sets a view param if its your profile, so the code for the same goes as follows,
if (list.get(position).getId()==PreferenceManager.getUserID())
{
// do some view change here
setViewParam(true);
}else
{
// reset the view change here
setViewParam(false);
}
So what you're doing here is giving recycled ViewHolder a chance to reset.
Do comment if you need help!
You can use this! setItemViewCacheSize(int size)
Check here RecyclerViewDocumentation.
The offscreen view cache stays aware of changes in the attached adapter, allowing a LayoutManager to reuse those views unmodified without needing to return to the adapter to rebind them.
First of all, can you share some more code please?
Second, why would you want to dynamically add new views on fly? Why don't you use different VIEWTYPE or just have those view already on your layout and just make them visible/invisible or visible/gone? (I believe it will be more efficient this way).
Let me remind you something about RecyclerView, yes when user is scrolling viewHolder are being reused (few of them can be created, even more than it needs to fill the screen). So if it happened that you added some views on "item A" and user scroll to "item Z", that viewHolder can be reused for that "item Z", hence the show up of the previously added views.
How can you solve that?
Well always check on every items if you need to add new views, if yes add them if not already added, else always remove those views (if present) to return to default viewHolder state (or whatever you call it).
Hope this will help you.
Save Information by tags for items with new child each time the Add newView operation occur. (In shared preference for example)
Tag: create with item position onBindViewHolder.
...
SharedPreference sharedPref = getSharedPreference("text" + position, context);
SharedPreference.Editor editor = sharedPref.edit();
editor.putString("view", "ImageView");
...
when load Adapter get this value and put default as null.
I am not sure about its efficiency but i will work.
...
String viewType = sharedPref.getString("view", null);
//it will return ImageView
if you know some possible viewTypes for example always going to be ImageView & TextView so with some if statement it will be ok.
if(viewType.equals("ImageVIew")){
item(position).addView(new ImageVIew(context));
}
Good Luck
In your adapter class of your recyclerView,
in the onBindViewHolder method,
create another adapter and do the same methods for your new adapter.
The hierarchy will be,
mainRecyclerView -> item1(->childRecyclerView1) , item2(->childRecyclerView2), item3(->childRecyclerView3)
This way you can achieve what you want without wrong values to be viewed on wrong items.
You should take any Empty Layout like Linearlayout in your child item layout XML and then add views into that LinearLayout of your particular item in this way when you scroll List all of you child views which you have added to LinearLayout also scroll with that item .
I've tried to change the background color of specific items in a ListView.
first, catch it from database:
ListAdapter adapter = new ArrayAdapter(getApplicationContext(),
android.R.layout.simple_list_item_1, db.getAllApps());
final ListView list = (ListView) findViewById(R.id.ListViewApps);
list.setAdapter(adapter);
then I will set all apps in different color, if they have the tag activated
// if app is activated in db --> set another colour in ListView
private void setAppCheck(ListView list) {
List<String> apps = db.getAllApps();
for (int i = 0; i < list.getCount(); i++) {
if (db.appActivated(apps.get(i)).equals("activated")) {
list.setBackgroundColor(0xffaaaaaa); // it changes ALL items...
} else {
// do nothing
}
}
}
And there is Problem, with list.setItemChecked(i, true) I can change it with a specific position, but how do I change the Background color of the specific Item in the ListView?
Hope you can help me.
The cleanest way to do what you're trying to do is writing your own CursorAdapter supporting two view types: activated apps and deactivated apps. Then in your getView method, when you're inflating your views, you can set the background color accordingly.
Having two item types will make the Android framework automatically pass only convert views of the correct type to getView, so the only time you need to check for the type is during creation.
You may find this answer helpful.
Adapter basics
In Android, Adapters are used to translate your data (in your case from an SQLite database) into Views that can be displayed in listviews, spinners, etc. (AdapterView to be specific). One of the most commonly used ones is the CursorAdapter which has basic infrastructure necessary when the associated data is supposed to be read from a cursor.
You will mainly need three methods in your adapter:
- getViewTypeCount which will tell the framework how many types of views your adapter knows. For you this will be two: activated and deactivated apps.
getItemViewType which, when passed a specific position in the data (here: the cursor), is able to decide which of those types that position falls into. For this, you will likely be able to reuse your db.appActivated code, at least in large parts.
getView, which, when passed a position, can turn the data associated with that position into a View for display. Let's look at that last part in more depth.
Android does some very nifty stuff to make sure your app is fast and slick and responsive. One of those things is, it will only keep enough views around for all positions in the list that are displayed. So, if you have a list that can display 10 items at a time, but your data holds a million records, it will still only keep 10 views around (well, actually, a few more from when stuff is scrolled off screen, but definitely not the one million it would require for every data record).
When the time comes to actually turn data into visible representations - getView - it will pass an old, previously visible but now off screen view (a recycled view) in as the convertView parameter for you to try adapting it to display the data that's been requeusted. This is because inflating new views is comparatively more expensive than just taking an existing one and changing its texts or images or whatever. The view types you have told it about will help it to only pass the type of convert view into getView that is appropriate for the position that's been requested.
This way, you need to only inflate a new view if the passed convert view is inappropriate somehow. And inappropriate, in this case, usually only means "if it is null". So, usually, what you end up with is something very close to this:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = // inflate a new view
}
// bind the convert view to the data, i.e. set its text views, images, and - in your case - background color
}
A video says more than a thousand words
You may want to watch this Google I/O keynote for a more comprehensive explanation of how it all ties together.