I'm fairly new to android programming. I've run into some issues loading images (bitmaps) into a ListView. It works flawlessly until I have around 5 images loaded, then it reuses the first image I loaded (assuming from cache). I've tried a lot of solutions and bitmap samplings but, still no luck. Here is my code:
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = getLayoutInflater().inflate(R.layout.listview_item, parent, false);
Contact currentContact = Contacts.get(position);
TextView name = (TextView) view.findViewById(R.id.contactName);
name.setText(currentContact.getName());
TextView phone = (TextView) view.findViewById(R.id.phoneNumber);
phone.setText(currentContact.getPhone());
TextView email = (TextView) view.findViewById(R.id.email);
email.setText(currentContact.getEmail());
TextView address = (TextView) view.findViewById(R.id.cAddress);
address.setText(currentContact.getAddress());
Context mContext = getApplicationContext();
File file = mContext.getDir("imageDir", Context.MODE_PRIVATE);
String path = "file:" + file.getPath() + "/" + currentContact.getImage() + ".png";
ImageView contactListImage = (ImageView) view.findViewById(R.id.contactListImage);
Picasso.with(getApplicationContext())
.load(path)
.centerInside()
.fit()
.error(R.drawable.user_2)
.into(contactListImage)
}
return view;
}
I did try using
recycleMemoryCache()
Thank in you advance!
You're not using the recycled view correctly.
The view parameter you have there is either null, which means you have to inflate a new one, or not null, which means you should re-use it - assign new values to all the fields etc.
You simply need to move the closing curly-brace above return view; to just after your view inflation:
if (view == null) {
view = getLayoutInflater().inflate(R.layout.listview_item, parent, false);
}
As an aside, I'd look up the viewholder pattern - or just start using RecyclerView, as that enforces it (and is the way of the future for lists on Android).
Related
I have a custom base adapter for my listview which I use view holder pattern. I have to change the source of several images dynamically. But when I change one of the images, after a couple of scroll whole images are changing. If I just remove the view holder pattern everything is just fine.
I searched some questions like: Facing critical issue: Toggle image source inside Listview using ViewHolder Pattern
But I couldn't find a proper way to do both.
Here is my getview() function.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
// if (convertView == null) {
holder = new ViewHolder();
convertView = this.inflater.inflate(R.layout.list_row2, parent, false);
// For every single item placed on XML
// Holder must be updated here
holder.codeName = (TextView) convertView.findViewById(R.id.codeName);
holder.level = (TextView) convertView.findViewById(R.id.level);
holder.pt1 = (TextView) convertView.findViewById(R.id.pt1Value);
holder.pt2 = (TextView) convertView.findViewById(R.id.pt2Value);
holder.tt3 = (TextView) convertView.findViewById(R.id.tt3Value);
holder.tt4 = (TextView) convertView.findViewById(R.id.tt4Value);
holder.lastUpdate = (TextView) convertView.findViewById(R.id.lastUpdate);
holder.status = (ImageView) convertView.findViewById(R.id.status);
convertView.setTag(holder);
// }
// else {
// holder = (ViewHolder) convertView.getTag();
// }
// Formatting level
SingleSystem temp = sys.get(position);
temp.seq1 = temp.seq1.replace("," , ".");
Float tempLevel = Float.parseFloat(temp.seq1);
Integer level = Math.round(tempLevel);
//Check if there is an alert on system
if(temp.seq13.contains("1") || temp.seq14.contains("1") ||
temp.seq16.contains("1") || temp.seq30.contains("1") || temp.seq31.contains("1") || temp.seq32.contains("1") ||
temp.seq45.contains("1") )
{
holder.status.setImageResource(R.drawable.process_warning); //If an alert, change running picture
}
//Set background color according to level
if(level<=15){
holder.level.setBackgroundColor(Color.rgb(255,17,0));
holder.status.setImageResource(R.drawable.offline);
}
else if(level>15 && level <30){
holder.level.setBackgroundColor(Color.rgb(255,128,0));
}
else{
holder.level.setBackgroundColor(Color.rgb(66,213,4));
}
// Check Time Out
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
try {
Date serverTime = dateFormat.parse(temp.serverTime);
Date communicationTime = dateFormat.parse(temp.communicationDate);
long diff = serverTime.getTime() - communicationTime.getTime();
diff = diff/1000;
diff = diff/60;
Integer cOut = Integer.parseInt(temp.communicationTimeOut);
if(diff > cOut){
holder.status.setImageResource(R.drawable.offline);
}
}
catch (Exception e){
e.printStackTrace();
}
return convertView;
}
As I said if I remove the if(convertView == null) part, everything is okey. For now I just have 16 rows but it may increase. Will it be big performance issue in the future ? How can I change my code to use view holder pattern ?
Thanks for your answer.
The views are reused, ie. they have all values/colors/drawables from when they were displayed before. You need to reset the image's drawable when reusing the view:
else {
holder = (ViewHolder) convertView.getTag();
holder.status.setImageDrawable(null);
}
I have a custom adapter for a list view. It has 3 fields: a thumbnail and two text fields.
Once the layout is inflated, the thumbnails are downloaded from an external server.
How do i change the functionality so that the images are downloaded only when the user scrolls down.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Inflate the layout
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.list_single, parent, false);
ImageView image = (ImageView) rowView.findViewById(R.id.picture);
TextView firstline = (TextView) rowView.findViewById(R.id.firstline);
TextView secondline = (TextView) rowView.findViewById(R.id.secondline);
// Get information about the report
AnimalLocationLog current = objects.get(position);
// Get all the important information
String species = current.getSpecies();
String sanitizedSpecies = MiscHelpers.sanitizeString(species);
int reportId = objects.get(position).getTrackerId();
// Construct the URL to the image
String imageLocation = websiteUrl + sanitizedSpecies + separator + reportId + extension;
// Display user report
downloader.download(imageLocation, image);
firstline.setText(species);
secondline.setText("Spotted " + current.getDateTime().toString("yyyy-MM-dd H:m"));
return rowView;
}
Add an OnScrollListener to your ListView with ListView.setOnScrollListener() and do the download in the ScrollListener.onScroll() method. You can use the ListView.getLastVisiblePosition() to figure out what list items are visible and which aren't.
I am trying to add dynamic textview to listview items.textviews can be of 1-2 or more than that depending on data I have succeed in adding the textview but problem is textviews are repeated on scroll.
I am creating new object of textview each time in loop.I am aware of the problem that android tries to reuse existing view but i have to add new view every time.
Here is my code in custom adapter:
public class ViewHolder {
TextView text1;
LinearLayout linearLayout;
TextView t;
TextView t1;
}
getView method
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.two_item_icon_text, null);
holder = new ViewHolder();
holder.text1 = (TextView) convertView.findViewById(R.id.text1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.linearLayout = (LinearLayout) convertView.findViewById(R.id.lin_lay_dynamic);
holder.text1.setText("" + DATA1[position]);
String tmp, dateparsed;
dateparsed = DATA1[position].substring(0, DATA1[position].indexOf(":"));
for (int x = 0; x < calendareventholder1.size(); x++) {
objHolder = (CalendarEventHolder) calendareventholder1.get(x);
if (objHolder.opendate.equals(displaydate[current])) {
tmp = objHolder.dtstarttime.toString().substring(0, objHolder.dtstarttime.toString().indexOf(":"));
if (Integer.parseInt(tmp) >= Integer.parseInt(dateparsed) && Integer.parseInt(tmp) < Integer.parseInt(dateparsed) + 1) {
holder.t = new TextView(convertView.getContext());
holder.t.setText(":-d ");
holder.t.setOnClickListener(this);
if (Common.isChildSessionAlerted(String.valueOf(objHolder.id), getApplicationContext(), object1)) {
holder.t.setText(holder.t.getText() + objHolder.dtstarttime + " " + objHolder.dtendtime + " :-a");
} else {
holder.t.setText(holder.t.getText() + objHolder.dtstarttime + " " + objHolder.dtendtime);
}
holder.t.setTag(objHolder.id);
holder.t.setTextSize(Common.getPreferenceInt(getApplicationContext(), Common.PREF_FONT_SIZE, 10));
holder.t.setTextColor(Color.BLACK);
holder.t.setText(getSmiledText(ScheduleActivity.this,
holder.t.getText().toString()));
holder.linearLayout.addView(holder.t);
holder.t1 = new TextView(convertView.getContext());
holder.t1.setOnClickListener(this);
holder.t1.setText(objHolder.title);
holder.t1.setTag(objHolder.id);
holder.t1.setTextSize(Common.getPreferenceInt(getApplicationContext(), Common.PREF_FONT_SIZE, 10));
holder.t1.setTextColor(Color.BLACK);
holder.linearLayout.addView(holder.t1);
}
}
}
return convertView;
}
Its a little hard to really understand what is in holder since it isn't super descriptive, but if I am reading your question right, the reason you are getting repeated items is because you are just adding Views and never deleting what was there before. ListView recycles views, meaning what was there previously stays there. As it scrolls it actually keeps reusing views rather than creating new ones. I won't go into detail because there are a bunch of question on here with similar problems. But at the top you should do
getView() {
/*previous stuff, and holder.linearLayout must have been set!*/
if(holder.linearLayout.getChildCount() > 0)
holder.linearLayout.removeAllViews();
This way any views already there will be removed. One last thing, you should also check out this. Immensely helpful in understanding why you have to do what I posted.
I have a database which has a name of an animal, and in an other column a sound of the animal.
The listview works fine, and now I would like to extend it with an image out of the assets.
For this I've read about how to get the assets data and use it.
An example I've tried worked fine for me.
Now I want this "assets" code (at least I think I want this) in the extended BaseAdapter class.
Unfortunately I'm doing something wrong as I can't use the getAssets() in the BaseAdapter.
The problem starts in the try-catch block: " getAssets " doesn't get recognized
Which way would I think of solving this?
Creating another class in which this assets code can run in an extended "Activity"?
Or are there better ways of showing an image via database info in a listview?
Thank you for your support in my quest to get familiar with Android / Java.
public View getView(int position, View convertView, ViewGroup parent) {
// get view reference
View view = convertView;
// if null
if(view == null) {
// inflate new layout
view = mInflater.inflate(R.layout.layout_list_item, null);
// create a holder
ViewHolder holder = new ViewHolder();
// find controls
holder.txtName = (TextView)view.findViewById(R.id.txtName);
holder.txtPicture = (ImageView)view.findViewById(R.id.txtPicture);
// set data structure to view
view.setTag(holder);
}
// get selected user info
UserInfo userInfo = mListUserInfo.get(position);
// if not null
if(userInfo != null) {
// query data structure
ViewHolder holder = (ViewHolder)view.getTag();
// set data to display
holder.txtName.setText(userInfo.getName() + ", " + userInfo.getPicture() );
try {
// get input stream
InputStream ips = getAssets().open( userInfo.getPicture() + ".jpg");
Log.d("Imageloading", "Reading: " + ips);
// load image as Drawable
Drawable d = Drawable.createFromStream(ips, null);
// set image to ImageView
holder.txtPicture.setImageDrawable( d );
}
catch(IOException ex) {
Log.e("Imageloading", "Could not load '" + ex.getMessage()+ "'!");
}
}
// return view
return view;
}
I've just edited the code.
To solve the getAssets() thing I've done the following:
holder.txtPicture.setImageDrawable( getSomePicture(null, userInfo.getPicture() + ".jpg") );
public Drawable getSomePicture(Context myContext, String WhichPicture) throws IOException {
// get input stream
InputStream ips = myContext.getAssets().open( WhichPicture );
Log.d("Imageloading", "Reading: " + ips);
// load image as Drawable
Drawable d = Drawable.createFromStream(ips, null);
return d;
}
This still is not the solution, researching some more....
Found an interesting source for Lazy Loading
to call getAssets() function in non-activity class you need reference to Context. In your updated code you called function 'getSomePicture()' and passed null to myContext parameter. that means your code will fail because you have myContext.getAssets() later in your method code.
try to do this in your getView method:
Context context = getContext();
holder.txtPicture.setImageDrawable( getSomePicture(context, userInfo.getPicture() + ".jpg") );
You won't have access to the getAssets() method because that is a method that only the Context (and it's subclass, Activity) have. What I find is easiest here is to make your BaseAdapter subclasses private inner classes of the Activity in which it is to be used. That way, you'll have access to the getAssets() method.
Here is what this might look like:
public class YourExampleActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
}
private class YourListAdapter extends BaseAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// get view reference
View view = convertView;
// if null
if(view == null) {
// inflate new layout
view = mInflater.inflate(R.layout.layout_list_item, null);
// create a holder
ViewHolder holder = new ViewHolder();
// find controls
holder.txtName = (TextView)view.findViewById(R.id.txtName);
holder.txtPicture = (ImageView)view.findViewById(R.id.txtPicture);
// set data structure to view
view.setTag(holder);
}
// get selected user info
UserInfo userInfo = mListUserInfo.get(position);
// if not null
if(userInfo != null) {
// query data structure
ViewHolder holder = (ViewHolder)view.getTag();
// set data to display
holder.txtName.setText(userInfo.getName() + ", " + userInfo.getPicture() );
try {
// get input stream
InputStream ips = getAssets().open( userInfo.getPicture() + ".jpg");
Log.d("Imageloading", "Reading: " + ips);
// load image as Drawable
Drawable d = Drawable.createFromStream(ips, null);
// set image to ImageView
holder.txtPicture.setImageDrawable( d );
}
catch(IOException ex) {
Log.e("Imageloading", "Could not load '" + ex.getMessage()+ "'!");
}
}
// return view
return view;
}
// Override other key methods of BaseAdapter here
}
}
Alternatively, you could take in a Context object in the constructor when the BaseAdapter subclass is instantiated, and keep a reference to that with which to call getAssets() on. Please post a comment if you need an example of this.
I am now stuck at a point and can't find any solution.
Description : I have a sqlite database having a table(channel table) which have 4 column
uid channel_name cid(country id) rating
uid is Unique channel id.
I have three images with star rating if star is yellow then channel have either 4 or 5 rating
if star is half yellow then channel rating is 2 or 3. if star is white then rating 1 will be considered.
I am showing these three images from gallery widgets in android. ant the channel name below star images. according to channel rating.
public View getView(int paramInt, View paramView, ViewGroup paramViewGroup) {
View view = null;
Cursor mCursor=db.dataChannelsName();
mCursor.moveToFirst();
String [] channels_name = new String[mCursor.getCount()];
for(int icount=0; icount<mCursor.getCount();icount++) {
channels_name[icount] = mCursor.getString(mCursor.getColumnIndex("name"));
mCursor.moveToNext();
}
if(paramView == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.items1, null);
ImageView image1 = (ImageView) view.findViewById(R.id.image1);
TextView description = (TextView) view.findViewById(R.id.description);
image1.setImageResource(mImageIds[paramInt]);
description.setTextColor(0xffffffff);
description.setText(channels_name[paramInt]);
} else {
view = paramView;
}
return view;
Question 1:
How will i show only these three images for all channel with different channels name.?
Question 2:
It can be possible to have same name channel in database
I am using sql queries and cursor for retrieving data.
Now my problem is when user will select the particular channel from the gallery then there is a possibility that there can be more than one channel with same name.
I just want to store the uid when user click on the channel name from the gallery??
EDIT I just solved the second question but i am stuck at question 1
any help would be appreciated.
Lets begin fixing some things, shall we?
public View getView(int paramInt, View paramView, ViewGroup paramViewGroup) {
View view = null;
Cursor mCursor=db.dataChannelsName();
mCursor.moveToFirst();
String [] channels_name = new String[mCursor.getCount()];
for(int icount=0; icount<mCursor.getCount();icount++) {
channels_name[icount] = mCursor.getString(mCursor.getColumnIndex("name"));
mCursor.moveToNext();
}
Now the getView Function is called everytime a listView displays a new Row (even when scrolling!!)...I'm assuming db.dataChannelName() fires a query SELECT * from channel (or worse, you might be opening and closing the database)....This is a very expensive operation, you should query it once in the constructor, store the array of channel names as a member variable and use it for the rest of the adapter.
Here's a concise way..
public View getView(int paramInt, View view, ViewGroup paramViewGroup) {
if(view== null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.items1, null);
}
ImageView image1 = (ImageView) view.findViewById(R.id.image1);
TextView description = (TextView) view.findViewById(R.id.description);
image1.setImageResource(mImageIds[paramInt]); // i'm assuming 0 - 0 star, 1 - 1 star, etc...
description.setTextColor(0xffffffff);
description.setText(channels_name[paramInt]); //remember channel_name was obtained in the constructor.
return view
}