I have a ListView that grabs news with images associated with it using Volley Json. At first it loads everything but when i scroll the list and come back, it sometimes shows images and sometimes not. After scrolling 2-3 times listview
loses every image. Same procedure works perfectly with my test app (With Actionbar Tabs Only) so i don't know what is happening. Thanks in adavance for your time.
Note: I use MySingleton and LruBitmapCache from Developers site. My app's GUI is similar like Play Store i.e Navigation Drawer with Tabs. I searched problem like this but found no direct answer. Sorry if it is silly question to ask here.
If I use ViewHolder then that problem gets resolved but I am unable to remove NetworkImageView programmatically if URL is not there.
I don't want to have placeholder image for row that doesn't have Image
Here is my layout file for row:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<com.android.volley.toolbox.NetworkImageView
android:id="#+id/cat_thumbnail"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentLeft="true"
android:layout_marginRight="8dp"
/>
<TextView
android:id="#+id/cat_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:layout_alignTop="#id/cat_thumbnail"
android:layout_toRightOf="#id/cat_thumbnail"
/>
<TextView
android:id="#+id/cat_paper"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/cat_title"
android:layout_marginTop="1dip"
android:layout_toRightOf="#+id/cat_thumbnail"
android:textSize="15dip" />
Adapter class
private Activity activity;
private LayoutInflater inflater;
private List<NewsArticles> newsArticlesItems;
ImageLoader imageLoader;
public NewsAdapter(Activity activity, List<NewsArticles> newsArticlesItems)
{
this.activity = activity;
this.newsArticlesItems = newsArticlesItems;
}
#Override
public int getCount() {
return newsArticlesItems.size();
}
#Override
public Object getItem(int location) {
return newsArticlesItems.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int postion, View convertView, ViewGroup viewGroup) {
if(inflater == null)
{
inflater = (LayoutInflater) activity.getSystemService(activity.LAYOUT_INFLATER_SERVICE);
}
if(convertView == null)
{
convertView = inflater.inflate(R.layout.cat_list_items, null);
}
if (imageLoader == null)
imageLoader = MySingleton.getInstance(activity).getImageLoader();
//Article image
NetworkImageView thumbnail = (NetworkImageView) convertView.findViewById(R.id.cat_thumbnail);
//Article title
TextView title = (TextView) convertView.findViewById(R.id.cat_title);
//Newspaper Name
TextView newspaperName = (TextView) convertView.findViewById(R.id.cat_paper);
//
if(!URLUtil.isValidUrl(newsArticlesItems.get(postion).getThumbnailUrl()))
{
thumbnail.setVisibility(View.GONE);
}
/*if(newsArticlesItems.get(postion).getThumbnailUrl()=="")
{
thumbnail.setVisibility(View.GONE);
}*/
else
{
thumbnail.setImageUrl(newsArticlesItems.get(postion).getThumbnailUrl(), imageLoader);
}
title.setText(newsArticlesItems.get(postion).getTitle());
newspaperName.setText(newsArticlesItems.get(postion).getNewspaperName());
return convertView;
}
Try like this in else block
else
{
thumbnail.setVisibility(View.VISIBLE);//add this
thumbnail.setImageUrl(newsArticlesItems.get(postion).getThumbnailUrl(), imageLoader);
}
Related
Here I am trying to make a music streaming app which works using an API, and I have utilized the MusicPlayer class available in Android. Here what I am trying to do is to get the response from the API in a listview and then click on any item in the listview to stream that particular song. The songs are present on a server and I am getting the correct responses from the server and music streaming is also implemented correctly according to me. The music progress is shown with the help of a SeekBar and I am 100% sure it is also working as I wanted it to be.
The new thing which I added to it recently is that, whenever I click on a particular song item from list, the headphone icon present on left of that item changes to play icon as shown in the screenshot attached below
Screenshot of the app from my phone
Here, play icon on left of songs show that they are recently played, but I want play icon only on those songs which is currently playing, not on recently played songs too.
But I cannot figure out way to do this, I have tried some things like
Storing the clicked item position somewhere in the class, to use it later.
Searching on internet, a way to modify only those list item which are not clicked in listview.
Modifying the image resource of the recently clicked item when stop icon is clicked.
But 3rd one didn't work, and haven't found a way to achieve the 1st and 2nd one..
Code snippet of ListView implementation
CustomSongList adapter = new CustomSongList(Songs.this, songdetails);
list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
url = songdetails.get(position).getUrl(); // your URl
pos = position;
ImageView listenImage = (ImageView) view.findViewById(R.id.musicicon);
listenImage.setImageResource(R.drawable.play); /* I am changing icon from headphone icon to play icon here*/
if (mediaPlayer != null) {
mediaPlayer.stop();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
mediaFileLengthInMilliseconds = mediaPlayer.getDuration();// gets the song length in milliseconds from URL
mediaPlayer.start();
imPlay.setVisibility(View.INVISIBLE);
imPause.setVisibility(View.VISIBLE);
primarySeekBarProgressUpdater();
}
});
Here is a code snippet of ListView Adapter class.
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.songlist_item, null, true);
TextView Title = (TextView) rowView.findViewById(R.id.title);
TextView Singer = (TextView) rowView.findViewById(R.id.singer);
TextView Duration = (TextView) rowView.findViewById(R.id.duration);
Title.setText(songlist.get(position).getTitle());
Singer.setText("Singer : " + songlist.get(position).getSinger());
Duration.setText("Duration : " + songlist.get(position).getDuration()+" mins");
return rowView;
}
And this is the snippet of songs layout file
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/seekBar"
android:layout_below="#+id/songheading"
android:layout_margin="10dp"
android:background="#android:color/transparent"
android:cacheColorHint="#android:color/transparent"
android:divider="#drawable/transperent_color"
android:dividerHeight="5dip" />
<SeekBar
android:id="#+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/buttonPlay" />
<ImageView
android:id="#+id/buttonPlay"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="#drawable/play" />
<ImageView
android:id="#+id/buttonPause"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="#drawable/pause" />
<ImageView
android:id="#+id/buttonStop"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:background="#drawable/stop" />
The headphone icon set for each item of listview is done by following code snippet
<ImageView
android:id="#+id/musicicon"
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="#string/app_name"
android:gravity="center"
android:layout_marginTop="10dp"
android:src="#drawable/listen_icon" />
Edit
Applied the solution given by #KalaBalik
thanks #KalaBalik for posting the steps to mark the song, but I have applied your steps step by step, and what I found is that my app now doesn't show the list itself, when loading is complete, here is the screenshot
And here are the edits performed by me on the code as you said
Code snippet of listview
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/seekBar"
android:layout_below="#+id/songheading"
android:layout_margin="10dp"
android:background="#android:color/transparent"
android:cacheColorHint="#android:color/transparent"
android:choiceMode="singleChoice" // -----here
android:divider="#drawable/transperent_color"
android:dividerHeight="5dip" />
CheckedTextView implementation
<CheckedTextView
android:id="#+id/tvChecked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="#drawable/playing" />
Compound drawable for checkedTextview,
playing.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/play" android:state_checked="true" />
<item android:drawable="#drawable/listen_icon" />
SpannableStringBuilder implementation
String finalString = title + "\n" + singer + "\n" + duration;
SpannableStringBuilder sb = new SpannableStringBuilder();
Typeface exoMedium = Typeface.createFromAsset(context.getAssets(), "fonts/Exo-Medium.ttf");
TypefaceSpan exoMediumSpan = new CustomTypeFaceSpan("", exoMedium);
sb.setSpan(exoMediumSpan, finalString.indexOf(title), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.setSpan(new ForegroundColorSpan(Color.BLUE), finalString.indexOf(singer), singer.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.setSpan(new ForegroundColorSpan(Color.CYAN), finalString.indexOf(duration), duration.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
I have created a CustomTypeFaceSpan using this link
setting SpannableStringBuilder in checkedTextView holder.checkedTextView.setText(sb); here I Used ViewHolder pattern
and setting listview checked true when item clicked
list.setAdapter(adapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
list.setItemChecked(position, true);
But result is in the screenshot above, tell me where I have done something wrong.
The code you linked does not contain all the changes I proposed. For example, songlist_item does not reference the selector. Most importantly, I do not want to deal with the details of MediaPlayer prepare callbacks. Therefore I made a minimal, yet complete and verifiable example of your problem. Please test and understand the code separately and then weave it into your project.
MainActivity:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView = findViewById(R.id.list);
ArrayList<String> songdetails = new ArrayList<>();
songdetails.add("Song1");
songdetails.add("Song2");
songdetails.add("Song3");
CustomArrayAdapter adapter = new CustomArrayAdapter(this, R.layout.listitem, songdetails);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
listView.setItemChecked(i, true);
// the selected song can be found at position i of songdetails
// start playing the selected song here
}
});
}
}
CustomArrayAdapter:
public class CustomArrayAdapter extends ArrayAdapter<String> {
private final Activity context;
private final int resource;
private ArrayList<String> songlist;
CustomArrayAdapter(Activity context, int resource, ArrayList<String> songlist) {
super(context, resource);
this.context = context;
this.resource = resource;
this.songlist = songlist;
}
#Override
public int getCount() {
return songlist.size();
}
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
final ViewHolder holder;
String song = songlist.get(position);
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null) {
convertView = inflater.inflate(resource, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.checkedTextView.setText(song);
return convertView;
}
static class ViewHolder {
final CheckedTextView checkedTextView;
private ViewHolder(View convertView) {
checkedTextView = convertView.findViewById(R.id.tvChecked);
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice" />
listitem.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tvChecked"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawableStart="#drawable/checkedstatelist" />
And checkedstatelist.xml (residing in res/drawable/):
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#android:drawable/ic_media_play" android:state_checked="true" />
<item android:drawable="#android:drawable/ic_media_pause" android:state_checked="false" />
</selector>
And this is the result:
What you need is to somehow mark or "check" the song currently playing. This can be done with a CheckedTextView, for example.
Put this in your ListView XML: android:choiceMode="singleChoice"
Inside your ListItem XML, I recommend having only one CheckedTextView. You can still do your formatting by (a) using a compound drawable for your image (for example: android:drawableStart="#drawable/playing") and (b) setting the text to a String from a SpannableStringBuilder programmatically. That way you can have different font sizes, styles and colors within one string which you put in the (one) CheckedTextView. In theory, you can have more complex listItem layouts, but it gets complicated quickly.
In your listener, you simply check the item at the current position, like so: list.setItemChecked(position, true);
Now that (after initialising) you always have one and just one checked list item, all you have to do is react to it. You can do so with a state list on the compound drawable. This state list would hold instructions about when (under which state: checked or not) one or the other drawable will be shown.
In order for the ListView to run efficient it is recommended (but not necessary) to implement the ViewHolder pattern, like so:
Inside the adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_item, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.checkedTextView.setText(filteredList.get(position));
return convertView;
}
static class ViewHolder {
final CheckedTextView checkedTextView;
private ViewHolder(View convertView) {
checkedTextView = (CheckedTextView) convertView.findViewById(R.id.listentry);
}
}
I have been reading for several hours for the solution, but I still have not found the correct answer. I have a ListFragment with a CursorAdapter. The rows are the following:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/circle"
android:src="#drawable/white_circle"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="italic"
android:textColor="#C0C0C0"
android:textSize="20sp"
android:id="#+id/tvStationName"
android:layout_toRightOf="#+id/circle"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignBottom="#+id/way"
android:gravity="top|start"
android:layout_marginLeft="5dp"/>
<ImageView
android:id="#+id/way"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="#+id/circle"
android:src="#drawable/white_way"/>
</RelativeLayout>
Since the ListFragment represents a route with stations (circle) and ways between the stations (way), I do not want a way to appear after the last station. Therefore, I change the image resource of the last listview element. The problem is the following:
If I scroll down the list, everything seems to be OK. Then, scrolling back to the top, at least one way is missing between the two middle stations, if it was the last element of the list. When I scroll more, I get more missing ways, and sometimes I get back some, it seems to be random.
The code of the adapter class is the following:
public class StationAdapter extends CursorAdapter {
private static final String TAG = "StationAdapter";
private Route route;
private Cursor storedCursor;
private LayoutInflater storedInflater;
private final class ViewHolder {
public TextView stationTv;
public ImageView stationIv;
public ImageView wayIv;
}
public StationAdapter(Context context, Cursor cursor, Route route) {
super(context, cursor, false);
this.route = route;
this.storedCursor = cursor;
this.storedInflater = LayoutInflater.from(context);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View row = inflater.inflate(R.layout.station_list_row, null);
bindView(row, context, cursor);
return row;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (storedCursor.moveToPosition(position)) {
ViewHolder holder;
if (convertView == null) {
convertView = storedInflater.inflate(R.layout.station_list_row, null);
holder = new ViewHolder();
holder.stationTv = (TextView)convertView.findViewById(R.id.tvStationName);
holder.stationIv = (ImageView)convertView.findViewById(R.id.circle);
holder.wayIv = (ImageView)convertView.findViewById(R.id.way);
convertView.setTag(holder);
}
else {
holder = (ViewHolder)convertView.getTag();
}
Station station = DbLoader.getStationByCursor(storedCursor);
// now we use the references
holder.stationTv.setText(station.getName());
if (station.equals(route.lastStation())) {
holder.wayIv.setImageResource(R.drawable.zero_way);
}
}
return convertView;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// UI elements
TextView stationTv = (TextView)view.findViewById(R.id.tvStationName);
ImageView wayIv = (ImageView)view.findViewById(R.id.way);
ImageView stationIv = (ImageView)view.findViewById(R.id.circle);
// station by cursor
Station station = DbLoader.getStationByCursor(cursor);
// set UI label
stationTv.setText(station.getName());
if (station.equals(route.lastStation())) {
wayIv.setImageResource(R.drawable.zero_way);
}
}
#Override
public Station getItem(int position) {
getCursor().moveToPosition(position);
return DbLoader.getStationByCursor(getCursor());
}
}
I did not want to cite too many details, I hope this will be enough.
Thanks in advance.
The issue is that the adapter tries to be more efficient. Reusing Views that you have scrolled away from.
You need to set the default position of your views, if you have a condition that changes them from their defaults.
So if you, for example, change a background image. When that view is recycled it will use the same image when it is reused.
if (station.equals(route.lastStation())) {
holder.wayIv.setImageResource(R.drawable.zero_way);
}
For example, needs to be reset to defaults.
if (station.equals(route.lastStation())) {
holder.wayIv.setImageResource(R.drawable.zero_way);
} else {
holder.wayIv.setImageResource(R.drawable.white_way);
}
I am using Spinner in one of my activity. Problem is that it shows its zero index element as title. because of this it appears twice, first in title and second is as first element of spinner. I don't want to give the selected option Which is in title in spinner drop down because its already selected So whats the use of it to give it as spinner first option. I want the selected option in title and rest of the option in Spinner list. Have a look at my code -
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
android:orientation="horizontal" >
<Spinner
android:id="#+id/spinner"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal|center_vertical" />
</LinearLayout>
and adapter class is -
public class SpinnerAdapter extends BaseAdapter {
Context mContext;
List<SpinnerContent> list;
public SpinnerAdapter(Context context, List<SpinnerContent> list) {
mContext = context;
this.list = list;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SpinnerContent item = list.get(position);
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflator.inflate(R.layout.spinner_item_row_image,
null);
}
TextView tvTitle = (TextView) convertView
.findViewById(R.id.tvSpinnerItem);
ImageView imgSpinnerContent = (ImageView) convertView
.findViewById(R.id.imgSpinnerItem);
if (item.getCollectionName().equalsIgnoreCase("Home")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_home);
tvTitle.setText("Home");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else if (item.getCollectionName().equalsIgnoreCase("One Level Up")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_spinner_up);
tvTitle.setText("One Level Up");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else if (item.getCollectionName().equalsIgnoreCase("One Level Down")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_spinner_down);
tvTitle.setText("One Level Down");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else {
if (position == 0) {
convertView.setBackgroundColor(mContext.getResources()
.getColor(android.R.color.transparent));
imgSpinnerContent.setVisibility(View.GONE);
tvTitle.setText(item.getCollectionName());
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
} else {
tvTitle.setText(item.getCollectionName());
applyTheme(convertView, mContext);
}
}
return convertView;
}
How can I achieve it. I want first element of list (Which is passed to spinner) as title but don't want to show it as first element of spinner.
Thanks in advance.
Its not possible with spinner. Spinner always shows the first element as title. If you want to add title then you better display your title at 0th position. In validation part if user selects 0th position set validation message to user.
I think you should use Kevin Chris solution.
Still if you want to achieve then try following trick.I never used this but it should work.
spinner.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
View convertView = spinner.getSelectedView();
View selectedView= adapter.getDropDownView(selectedPosition, convertView, spinner);
selectedView.setVisibility(View.GONE);
}
});
I trying to make a listview which contains a view in every row. This view contains 2 textviews and 1 gridview which is 2 columns. In every column I use a basic layout which is consist of 2 textviews.
This is preview of basic layout which is used in every block of gridview.
Here is its xml; -First view-
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item">
<TextView
android:layout_width="220dp"
android:layout_height="wrap_content"
android:textSize="18dp"
android:text="Item Name"
android:id="#+id/list_item"
android:maxLines="1"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Price"
android:textSize="18dp"
android:id="#+id/item_price"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="5dp"/>
</RelativeLayout>
Here is my second view which contains 2 textview and 1 gridview.
Here its xml; -Second view-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40dp"
android:text="Kategori"
android:id="#+id/categories_title_list_layout"/>
<GridView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/gridView_list_layout"
android:numColumns="2"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not: Lorem ipsum dolor sit amet.."
android:textSize="18dp"
android:id="#+id/categories_note"/>
</LinearLayout>
Here is my last view; -Third view-
This listview's every row takes shape of my second view which takes shape of first view.
Here is my adapter which are create this views.
For first view I use this adapter;
public class ItemListAdapter extends BaseAdapter {
private Context context;
private ArrayList<Item> items;
public ItemListAdapter(Context context, ArrayList<Item> items) {
super();
this.context = context;
this.items = items;
}
public int getCount() {
return items.size();
}
public Object getItem(int i) {
return items.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if(view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.listview_item_content, null);
holder.title = (TextView) view.findViewById(R.id.list_item);
holder.price = (TextView) view.findViewById(R.id.item_price);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face=Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
holder.title.setText(items.get(i).getName());
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.price.setText(Global.getLocalizedPriceStringByLocale(Shop.getLocale(), items.get(i).getPrice()));
holder.price.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
return view;
}
public class ViewHolder {
public TextView title;
public TextView price;
}
}
(This adapter takes item's title and price and puts them in first view.)
My second adapter which create second view is here;
public class CategoryListAdapter extends BaseAdapter {
private Context context;
//private ArrayList<Item> items;
private ArrayList<Category> currentCategory;
public CategoryListAdapter(Context context, ArrayList<Category> category) {
super();
this.context = context;
currentCategory = category;
}
public int getCount() {
return currentCategory.size();
}
public Object getItem(int i) {
return currentCategory.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.grid_list_layout, null);
holder.title = (TextView) view.findViewById(R.id.categories_title_list_layout);
holder.gridView = (GridView) view.findViewById(R.id.gridView_list_layout);
holder.note = (TextView) view.findViewById(R.id.categories_note);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
if(currentCategory.get(i).getName().equals("")){
holder.title.setText("Diğer");
}else{
holder.title.setText(currentCategory.get(i).getName());
}
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.note.setText(currentCategory.get(i).getDeepNote());
holder.note.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.gridView.setAdapter(new ItemListAdapter(context, currentCategory.get(i).getItems()));
holder.gridView.setBackgroundColor(Color.parseColor(Shop.getInstance().getItemGridBackgroundColor()));
holder.gridView.getBackground().setAlpha(180);
return view;
}
public class ViewHolder {
public TextView title;
public GridView gridView;
public TextView note;
}
}
And I use this adapter to create ListView.
Here is my problem. This works really slow. I mean ListView freezes for a moment then slide down when I try to move down.
And there is another problem about GridView's height. My GridView's height is wrap_content but it doesn't behave like wrap_content. It shows bigger or smaller GridView.
For example; under "Diğer" title there should be a GridView which contains only 1 item as you can see, but it can not show the complete text. And under "Adet Ürünler" there should be 190 items but it only views 20 of them.
These are my problems. Sorry for my coding. If you can not understand my code, please ask me.
Thanks for your helps.
This answer won't give you a definitive solution, not because I'm not willing, but because it's impossible (and even harder without not just viewing your code, but knowing it very well). But from my experience I can tell you that those kind of memory leaks doesn't occur just due to directly referenced objects - objects you declare (and keep referencing another classes/objects) in turn depends on many other classes and so on, and probably you're seeing a memory leak due to an incorrect handling of any of your instances which at the same time reference other instances.
Debugging memory leaks is a often a very hard work, not just because as I said above it sometimes doesn't depend directly on what you've declared, but also because finding a solution might not be trivial. The best thing you can do is what you already seem to be doing: DDMS + HPROF. I don't know how much knowledge you have, but although it's not a universal method, this link helped me so much to find memory leaks in my code.
Although it seems trivial, the best way to debug those kind of things is progresively remove portions of your code (overall, those which implies working with instances of other classes) and see how the HPROF report change.
as far as i understand your problem, it's low performance during scrolling the list you've shown in the screenshot?
a solution to your problem would need you to rewrite your adapter and layouts. Your performance problem is due to the rather large list-items you use (e.g. a grid inside a single list item with 190 items), that have to be loaded during scrolling the list, together with a rather low re-usability of list-items.
in addition, i prefer not to use view-holders.
To get rid of the grids, you could use a list of objects (or wrappers like shown below), that contains the 'title', 'note' and the single grid rows in between. you would have to overwrite some of the adapters methods, to use multiple viewtypes inside one listview (like shown below).
perhaps you'll also need some more code, to map your model into the new list, but after all, your performance should be back to normal.
only disadvantage i know of (and have no fast solution for) is due to the different height in single list items, the scrollbar of the whole list shows sometimes a strange behaviour (like: height of scrollbar indicator changes during scroll)
wrapper
public class ListItemWrapper {
ListItemType type;
Object content;
public ListItemWrapper(ListItemType type, Object content) {
this.type = type;
this.content = content;
}
public enum ListItemType { Title,Note, Content;}
}
ListAdapter
public class ListAdapter extends ArrayAdapter<ListItemWrapper> {
private LayoutInflater inflater;
public ListAdapter(Context context, int resource) {
super(context, resource);
inflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ListItemWrapper item = getItem(position);
switch (item.type) {
case Content: return getViewForGridRow((GridRowContent)item.content, convertView);
case Note: return getViewForNote((String)item.content, convertView);
case Title: return getViewForTitle((String) item.content, convertView);
}
return convertView; //this case should never happen
}
private View getViewForTitle(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the title
convertView = inflater.inflate(R.layout.titleRowLayout, null);
}
//TODO set textview value for the title
return convertView;
}
private View getViewForNote(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the note
convertView = inflater.inflate(R.layout.noteRowLayout, null);
}
//TODO set textview value for the note
return convertView;
}
private View getViewForGridRow(GridRowContent item, View convertView) {
if (convertView == null) {
//TODO inflate a new view for a single grid row
convertView = inflater.inflate(R.layout.gridRowLayout, null);
}
//TODO set values of the grid row (e.g. textview items)
return convertView;
}
#Override
public int getItemViewType(int position) {
return getItem(position).type.ordinal();
}
#Override
public int getViewTypeCount() {
return ListItemType.values().length;
}
}
I simplified parts of the model, but i hope you'll get the point.
as you can see, i don't use a custom BaseAdapter, since the arrayadapter already manages my collection. i only need to tell the list, that there are different item view types and which item in the collection uses which view type.
Additionally, there's no need to use any holders, since all holding and managing the different views is already done by the adapter - e.g. we only need to inflate views if the given convertView is not already cached within the adapter.
using this way should be much more memory efficient and performance should increase, since much less views have to be inflated in a single step.
I hope this helps,
Christian
Edit
can't explain the disapearing gridview in the center, but that shouldn't happen anymore
I've a problem with a listview and shuffle image.
Premise: I know that the cells/rows are re-used (recycled)
My cell is made up of an image and some textview (to simplify we suppose, only one image and only one tetxview)
if the image is present on the net (I know that by a bussines logic...) it's downloaded from the net (correct image); otherwise I put an image from application's bundle (default image)
in this way, i've a shuffle images problem.
Instead, if I load the defualt image always from the net, it's work fine.
My question is: why? why if i load default-image from the net it's ok, but if i load default-image from the app's bundle I've shuffle images problem?
Thanks in advance.
Code of MyAdaper
public class MyArrayAdapter extends ArrayAdapter<MyObject> {
final String URL_DEFAULT_IMG_USER="https://xxxxxxxxxxx/xxxx/default.png";
private LayoutInflater inflater;
private List<MyObject> data;
LinearLayout l1=null;
Context context=null;
public MyArrayAdapter(Context context,List<MyObject> objects) {
super(context, R.layout.item_myobject, objects);
inflater= LayoutInflater.from(context);
this.data=objects;
this.context=context;
BitmapManagerNoCompressionUser.INSTANCE.setPlaceholder(BitmapFactory.decodeResource(
context.getResources(), R.drawable.user));
}
//ViewHolder class that hold over ListView Item
static class ViewHolder{
//ImageView image;
ImageView image;
TextView displayName;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = inflater.inflate(R.layout.item_myobject, null);
holder =new ViewHolder();
holder.image=(ImageView) convertView.findViewById(R.id.imageView);
holder.displayName =(TextView) convertView.findViewById(R.id.textViewDisplayName);
convertView.setTag(R.id.imageViewMyObject, holder.image);
convertView.setTag(R.id.textViewDisplayName, holder.displayName);
//set tag of convertView to the holder
convertView.setTag(holder);
}
else {
holder =(ViewHolder) convertView.getTag();
/* THIS CODE IS IRRELEVANT FOR THE SUFFLE PROBLEM, BUT I DONT'KNOW IF IT IS NECESSARY
holder.image=(ImageView) convertView.getTag(R.id.imageViewMyObject);
holder.displayName =(TextView) convertView.getTag(R.id.textViewDisplayName);
*/
}
holder.image.setTag(position); // This line is important.
if(data.get(position).getPathFotoServer()!=null ) {
BitmapManagerNoCompressionUser.INSTANCE.loadBitmap(data.get(position).getPathFotoServer(), holder.image, 100,100);
} else {
BitmapManagerNoCompressionUser.INSTANCE.loadBitmap(URL_DEFAULT_IMG_USER, holder.image, 100,100); //IT'S OK
//holder.image.setImageResource(R.drawable.default); //WITH THIS LINE, SHUFFLE IMAGE PROBLEM
}
holder.displayName.setText((CharSequence) data.get(position).getDisplayName());
return convertView;
}
}
Code of item_myobject
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout1"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:layout_gravity="fill_vertical"
android:background="#drawable/state_item_listview"
>
<!-- android:layout_margin="5dp" -->
<ImageView
android:id="#+id/imageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="5dp"
android:src="#drawable/user" />
<TextView
android:id="#+id/textViewDisplayName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Medium Text"
android:gravity="left"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
Update: add the code for sort items in the activity:
Collections.sort(data, new Comparator<MyObject>(){
public int compare(MyObject s1, MyObject s2) {
Log.d(TAG, "s1.getDisplayName().compareToIgnoreCase(s2.getDisplayName():"+s1.getDisplayName()+" "+s2.getDisplayName());
return s1.getDisplayName().compareToIgnoreCase(s2.getDisplayName());
}
})
If you want to load images in your listview from server then use android query for performing this, it will manage every thing for you. You don't have to bother for any thing.
check this link for example with code
This is your problem:
else {
BitmapManagerNoCompressionUser.INSTANCE.loadBitmap(URL_DEFAULT_IMG_USER, holder.image, 100,100); //IT'S OK
//holder.image.setImageResource(R.drawable.default); //WITH THIS LINE, SHUFFLE IMAGE PROBLEM
}
Image loading from web it is'nt a quick operation, and while it's running, adapter set image to your adapter from it's own cache.
For this situations i use often a viewSwitcher - when i start loading data i show a progress bar while image is loading. For example - here my code:
Explantations
- newsItem is my adapter item which contains image_url call's "pict" and "image" as my bitmap image. Also in newsItem i create an AsyncTask which will load image from url, and after setting an ArrayList to my adapter i've start an asyncTask and show progressBar in display, if picture was downloaded sucessfuly - i show an image.
if (newsItem.pict.length() != 0 &&
!newsItem.isTaskWasStarted() &&
newsItem.image == null) {
loadImage(newsItem, viewItem);
} else if (newsItem.image != null) {
viewItem.image.setImageBitmap(newsItem.image);
viewItem.flipper.setDisplayedChild(1);
} else if (newsItem.pict.length() == 0) {
viewItem.flipper.setDisplayedChild(0);
} else if (newsItem.image == null && newsItem.isTaskWasStarted()) {
viewItem.flipper.setDisplayedChild(0);
}