I have a lot of PNG images contained in the various Drawable folders (Drawable-xhdpi, Drawable-mdpi, etc.) They are all relatively small (at most like 10KB) but at one point I need to load about 50 of them onto the screen. When I do this, it causes an OutOfMemoryError. Ideally, I would like to be able to load these images by simply calling setContentView once (the content view has a bunch of ImageViews with their src attribute already set to the corresponding images). That's what I'm doing now, but of course this isn't working because of the memory error. Besides reducing the size of the images, is there any way to prevent the OutOfMemoryError?
Avoid loading this number of images all at once,
instead you can load them in a GridView as described here.
Use Picasso with GridView for memory efficiency
public View getView(int position, View convertView, ViewGroup container) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
imageView = (ImageView) convertView;
}
// Load image into ImageView "using Picasso"
Picasso.with(mContext).load(imageResIds[position]).into(imageView);
return imageView;
}
Related
In my application i have a gallery, a gridview containing imageviews. The code is as follows:
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(LAYOUT_WIDTH, LAYOUT_HEIGHT));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
BitmapFactory.Options o = new BitmapFactory.Options();
o.inSampleSize = 5;
Bitmap previewBitmap = Bitmap.createScaledBitmap(
BitmapFactory.decodeFile(picture_paths[position], o),
LAYOUT_WIDTH,
LAYOUT_HEIGHT,
false);
imageView.setImageBitmap(previewBitmap);
return imageView;
}
Ofcourse, the performance of this is pretty much horrible as it has to resize and load a bitmap every time an element is drawn. However, if i move the resize-code inside the if(convertView == null) block, the order of the elements is not maintained - every image changes position constantly as the user scrolls through the gridview.
What is the proper way to do this without having performance issues?
Every time you get a convertView that's not null, you need to rebind data. This means you need to keep a reference to your scaled bitmaps somewhere else (in your adapter, in a bitmap cache, etc.)
I have already searched through SO, and found 4 similar questions but it seem this problem is different kind. I also spent about about 7 hours to solve this single bug, and didn't found where I did wrong.
THE PROBLEM:
Every row in ListView contain two images. When I launch the activity, either one or both of imageView in first row displaying wrong image and this only happen at sometimes after launch. Where, sometimes, the row displaying correct images after launch.
When I scroll down hiding the first row, and then scroll up again, the images change to correct images.
TRIED SOLUTION:
I found only one solution, is to set convertView = null at the beginning of getView method. Yes, it's works, imageView display correctly, but leading to other problem and bug. Scrolling become not smooth and sometimes some images became too small from what it should be.
NOTE:
There is no problem with data, data fetching correctly for every rows, after i run debug.
MY CODE
MatchAdapter.class
getView method
public View getView(int position, View convertView, ViewGroup parent) {
Match entry = listMatch.get(position);
MatchAdapter.ViewHolder myViewHolder;
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.match_row, parent, false);
myViewHolder = new MatchAdapter.ViewHolder();
myViewHolder.home_team_name = (TextView) convertView.findViewById(R.id.home_team_name);
myViewHolder.away_team_name = (TextView) convertView.findViewById(R.id.away_team_name);
myViewHolder.home_logo_view = (ImageView) convertView.findViewById(R.id.home_team_logo);
myViewHolder.away_logo_view = (ImageView) convertView.findViewById(R.id.away_team_logo);
myViewHolder.venue = (TextView) convertView.findViewById(R.id.venue);
myViewHolder.time = (TextView) convertView.findViewById(R.id.time);
convertView.setTag(myViewHolder);
} else {
myViewHolder = (ViewHolder) convertView.getTag();
}
ImageLoader imageLoader = new ImageLoader(this.context, "com.myapps", R.drawable.team_logo);
imageLoader.DisplayImage("http://myapps.com/assets/images/logos/"+ entry.getHome_team_logo(),
myViewHolder.home_logo_view);
imageLoader.DisplayImage("http://myapps.com/assets/images/logos/" + entry.getAway_team_logo(),
myViewHolder.away_logo_view);
myViewHolder.home_team_name.setText(entry.getHome_team_name());
myViewHolder.away_team_name.setText(entry.getAway_team_name());
myViewHolder.venue.setText(entry.getVenue());
myViewHolder.time.setText(entry.getTime());
return convertView;
}
Other related code:
ImageLoader.class (I don't want this question become too long to scroll)
Thank you for your help!
My guess is the following code is
Match entry = listMatch.get(position);
too early. ListView's getView is multithreaded and It take some time in inflate the layout to convert view. And somehow the variable reference became incorrect.
Call Match entry = listMatch.get(position); after
else {
myViewHolder = (ViewHolder) convertView.getTag();
}
Problem solved! I redesign my whole code using lazylist, following example by fedor Simple demo of a LazyList
I found it from here How do I do a lazy load of images in ListView
I am working on a sample application in which I need to get the resource of an image view in a onClick listener and compare that with the image source that I know exists. If the resources are the same, I want to launch another intent. The problem I am facing right now is to access that ImageView (and hence its resource Id integer) to compare to the drawable resource.
#Override
// should int be final ??
public View getView(final int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// This is not working and I need to find a way to solve this >>
if (((ImageView)v).getResources().getInteger(0) == R.drawable.imageToCompare)
{
// do nothing
}
else
{
// do something
}
You can't get a drawable id from an ImageView. But if you set it from code, you can also store it somewhere, for example in the tag field. Take a look at the similar question: Who I compare an background Image resource TextView with a R.drawable.bg_image for switch.
if(((ImageView)v).getDrawable().getConstantState() == MainActivity.this
.getResources().getDrawable(R.drawable.unlock)
.getConstantState()))
{
// Do Something
}
The proper way is to user setTag() and getTag. I guess you are making your own adapter.
In the method public View getView(int position, View convertView, ViewGroup parent), you set tag to Imageview when you add other properties to it.
ImageView temp = (ImageView) v.findViewById(resId);
temp.setImageResource(icon);
temp.setTag(icon); //drawable unique ID will be my identifier here
Then in the onClick you simply compare View v with your drawable id. For example, I wanted to change image on click and I coded it this way. Take notice how I also change tag when I set a new drawable
img.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if ((Integer)v.getTag() == R.drawable.current_img) {
v.setBackgroundResource(R.drawable.new_img);
v.setTag(R.drawable.new_img);
}
}
});
You can use this.
if((pic1.getDrawable().getConstantState() == getResources().getDrawable(R.drawable.addpics).getConstantState()))
{
//do something
}
I guess you have all tried "Google Places" in Maps.
This is a list of POI close to you.
I really would like to do the same feature in my application with a list of GPS coordinates, but that seem really complicated.
Making the listview with the distance and the little arrow is very easy, but I cannot understand how to update this list and the arrow everytime the user move his phone.
For now I have a static listview.
I would like to know if someone succeed to create such a listview.
Thank a lot for any information.
There are a few options for what you're trying to achieve. Personally, I'd recommend implementing your own Adapter (most likely in this case by extending SimpleCursorAdapter) and encapsulating the updates to distance text and compass heading rotation within that.
To help with resource management, you probably want to create a SensorListener and LocationListener within the Activity that will host the ListView. Whenever you receive an update call your self-rolled updateCompassAndLocation method from within your Adapter class.
From within that method you have two options. Either iterate over each of the items that make up the data collection being represented and modify the compass graphic and distance text, or simply record the current location and heading as variables within the class, and call notifyDataSetChanged to force the adapter to update the views itself within the getView method. In either case (especially the latter), you'll need to set the distance text and heading compass values within getView.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout myView;
MyPOI item = getItem(position);
Location poiLocation = item.getLocation;
int compassHeading = // Calculate heading relative to current heading
float distance = // Calculate distance to POI
if (convertView == null) {
myView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi = (LayoutInflater)getContext().getSystemService(inflater);
vi.inflate(resource, myView, true);
} else {
trainView = (LinearLayout) convertView;
}
TextView distanceView = (TextView)trainView.findViewById(R.id.distance);
ImageView compassView = (ImageView)trainView.findViewById(R.id.compass);
distanceView.setText(String.valueOf(distance);
compassView.setImageLevel(compassHeading);
}
I'm having problems implementing an asynchronous image loader to the following code. I read some posts around the web about it and I think I understand the logic behind it, but I seem to fail in implementing it.
The code bellow is what I use to simply load the images in my listview.
public class MyCustomAdapter extends ArrayAdapter<RSSItem> {
Bitmap bm;
public MyCustomAdapter(Context context, int textViewResourceId, List<RSSItem> list) {
super(context, textViewResourceId, list);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
bm = LoadImage(myRssFeed.getList().get(position).getDescription(), bmOptions);
View row = convertView;
if(row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.rsslist, parent, false);
}
TextView listTitle = (TextView)row.findViewById(R.id.listtitle);
listTitle.setText(myRssFeed.getList().get(position).getTitle());
ImageView listDescription = (ImageView)row.findViewById(R.id.listdescription);
listDescription.setImageBitmap(bm);
TextView listPubdate = (TextView)row.findViewById(R.id.listpubdate);
listPubdate.setText(myRssFeed.getList().get(position).getPubdate());
return row;
}
}
You may use my sample code as reference Lazy load of images in ListView
Have you looked SmartImageView?
http://loopj.com/android-smart-image-view/
It's very simple library to load images asynchronously (:
some Features of this library
Drop-in replacement for ImageView
Load images from a URL
Load images from the phone’s contact address book
Asynchronous loading of images, loading happens outside the UI thread
Images are cached to memory and to disk for super fast loading
SmartImage class is easily extendable to load from other sources
On solution would be to populate a class variable within your adapter, say, an ArrayList with the references all the "ImageView listDescription"
ArrayList<ImageView> allImageViews = new ArrayList<ImageView>();
...
public View getView(int position, View convertView, ViewGroup parent){
...
ImageView listDescription=(ImageView)row.findViewById(R.id.listdescription);
allImageViews.add(listDescription);
...
}
private class ImageDownLoader extends AsyncTask<ArrayList, Void, Void>{
doInBackground(){
for(ImageView imageView: allImageViews){
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
bm = LoadImage(imageNameOrWhatever, bmOptions);
imageView.setImageBitmap(bm);
}
}
Then use an AsyncTask that goes through each ImageView, retrieves the associated Image and removes the ImageView from the ArrayList. It will download one at a time in the background while your gui will still respond.