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.)
Related
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;
}
Hi i'm having this kind of problem, when scrolling imageviews change their positions and
their background images. I saw other answers on this topic on this site, but non of them
helped me.
Like this once:
grid view scrolling issue
GridView scrolling problem on Android
GridView elements changes their place dynamically when scroll screen
and many others...but they don't solve my problem.
Important thing is that i don't use custom layout for gridview or gridview items(imageViews).I create them programmatically. This is very important to me so if someone know the answer pls help me...Thanks.
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
imageView = new ImageView(ctx);
} else {
imageView = (ImageView) convertView;
}
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
imageView.setBackgroundResource(tmp[position]);
imageView.setImageResource(blank);
return imageView;
}
Its a simple method. Adapters recycle each and every view while scrolling. So we just need to create the ones which are being re-used after the first time. Holders help to avoid expensive calls like findViewById and re-use the items by just getting the old ones and changing its properties.
One point you need to keep in mind is that we need to hold the images which are to be displayed in any one of the containers like array[] or List and reset each time before returning the view, or else it will display the previously last recycled view.
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_layout, null);
holder = new ViewHolder();
holder.cover = (ImageView) convertView
.findViewById(R.id.item_cover);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.cover.setBackgroundResource(tmp[position]);
return convertView ;
}
static class ViewHolder {
ImageView cover;
}
try this
class Holder {
ImageView imageView;
}
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
if(holder.imageView==null){
holder=new Holder();
holder.imageView = new ImageView(ctx);
holder.imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.imageView.setPadding(8, 8, 8, 8);
imageView.setTag(holder);
} else {
holder= (Holder) imageView.getTag();
}
holder.imageView.setBackgroundResource(tmp[position]);
holder.imageView.setImageResource(blank);
return imageView;
}
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'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.