I have a dilemma using
if (convertview==null){
(my code)
}
or not. Without this piece of code, my listview is not very fast, it locks for a few ms sometimes and you can easy notice this in use. It just doesn't work how its meant to work.
But when this piece of code, my listitems will start recounting after a while (10 or so) and i have a few returning listitems in my list (with the header i used). I used this tutorial for getting my listview with sections link. The length of the list is good.
Ofcourse my list is totally useless with a view repeating items (nicely sectioned by the way) but i also dont want it to be slow.
Does anyone know what to do?
Below is my adapter:
public class DelftAdapter extends BaseAdapter {
private Activity activity;
private List<ListItem> listItems;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
private final int[] bgColors = new int[] { R.color.list_odd, R.color.list_even };
public DelftAdapter(Activity a, ArrayList<ListItem> li) {
activity = a;
listItems = li;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return listItems.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
final ListItem li = listItems.get(position);
if (li != null) {
if(li.isSection()){ // is sectionheader
SectionItem si = (SectionItem)li;
vi = inflater.inflate(R.layout.sectionedlistitem, null);
vi.setOnClickListener(null);
vi.setOnLongClickListener(null);
vi.setLongClickable(false);
final TextView sectionView = (TextView) vi.findViewById(R.id.list_header_title);
sectionView.setText(si.getTitle());
}else{ // no sectionheader
ListData ld = (ListData)li;
vi = inflater.inflate(R.layout.singlelistitem, null);
TextView tvNames=(TextView)vi.findViewById(R.id.tvname);
TextView tvTip=(TextView)vi.findViewById(R.id.tvtip);
ImageView image=(ImageView)vi.findViewById(R.id.image);
tvNames.setText(ld.name);
tvTip.setText(ld.tip);
if (listItems.get(position) != null ){
imageLoader.DisplayImage(ld.photoUrl, image);
}
else{
image.setImageURI(Uri.fromFile(new File("//assets/eten.png")));
}
// alternating colors
int colorPos = position % bgColors.length;
vi.setBackgroundResource(bgColors[colorPos]);
}
}
return vi;
}
}
Consider using getItemViewType() and getViewTypeCount() with recycling convertView. Those are used when you have list items with various layouts. You definitely should recycle convertView.
See also http://android.amberfog.com/?p=296
In your case:
private static final int TYPE_ITEM = 0;
private static final int TYPE_SECTION = 1;
#Override
public int getItemViewType(int position) {
return listItems.get(position).isSection() ? TYPE_SECTION : TYPE_ITEM
}
#Override
public int getViewTypeCount() {
return 2; // sectionheader and regular item
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
if (convertView == null) {
switch (type) {
case TYPE_ITEM:
convertView = mInflater.inflate(R.layout.singlelistitem, null);
...
break;
case TYPE_SECTION:
convertView = mInflater.inflate(R.layout.sectionedlistitem, null);
...
break;
}
} else {
...
}
return convertView;
}
Also use ViewHolder pattern to achieve better performance.
Related
I have looked at other threads and I cannot see what is wrong with my listadapter. I have the view holder pattern and I am setting all the values for the list item outside of the convertview null check statement. Im not sure what other things would cause it to repeat the items.
public class ScheduledJobListAdapter extends BaseAdapter {
private static LayoutInflater inflater = null;
private ArrayList<Job> jobList;
private Context context;
public ScheduledJobListAdapter(Context context, ArrayList<Job> jobList) {
this.context = context;
this.jobList = jobList;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() { return jobList.size(); }
#Override
public Job getItem(int position) { return jobList.get(position); }
#Override
public long getItemId(int position) { return 0; }
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ScheduledViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_item_scheduled_job,null);
holder = new ScheduledViewHolder();
holder.job = getItem(position);
holder.container = convertView.findViewById(R.id.scheduled_job_layout);
holder.routeOrder = convertView.findViewById(R.id.route_order_textview);
holder.location = convertView.findViewById(R.id.location_textview);
holder.jobRef = convertView.findViewById(R.id.job_ref_textview);
holder.statusIndicator = convertView.findViewById(R.id.status_indicator);
convertView.setTag(holder);
} else {
holder = (ScheduledViewHolder) convertView.getTag();
}
holder.routeOrder.setText(holder.job.getRouteOrder() + "");
holder.location.setText(holder.job.getLocation());
holder.jobRef.setText(holder.job.getJobReference());
return convertView;
}
}
class ScheduledViewHolder {
Job job;
LinearLayout container;
TextView routeOrder;
TextView location;
TextView jobRef;
ImageView statusIndicator;
}
Here's the problem:
holder.job = getItem(position);
Remember the views may be recycled as you scroll, and the job assigned maybe used unintentionally for other positions if you assigned it that way. To fix it, simply assign the job after the if-else condition:
if (convertView == null) {
// ...
} else {
// ...
}
holder.job = getItem(position); // Update the job by position
holder.routeOrder.setText(holder.job.getRouteOrder() + "");
holder.location.setText(holder.job.getLocation());
holder.jobRef.setText(holder.job.getJobReference());
I am working on a project which require me to develop a autocomplete input suggestion box the problem is the suggestion items are categorized they each category is supposed to be highlighted and unclickable.
I have implemented custom ArrayAdapter for this purpose but could't figure out how to make categories un clickable
here is my custom array adapter code
public class CustomAutoCompleteAdapter extends ArrayAdapter<String> {
private static final int TYPE_ITEM = 0;
private static final int TYPE_CATEGORY = 1;
private TreeSet<Integer> sectionHeader = new TreeSet<Integer>();
private LayoutInflater mInflater;
public CustomAutoCompleteAdapter(#NonNull Context context, #LayoutRes int resource, #NonNull List<String> objects) {
super(context, resource, objects);
mInflater = LayoutInflater.from(context);
}
public int getItemViewType(int position) {
return sectionHeader.contains(position) ? TYPE_CATEGORY : TYPE_ITEM;
}
public void addHeader(String item) {
super.add(item);
sectionHeader.add(this.getCount() - 1);
}
#Override
public int getViewTypeCount() {
return 2;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ViewHolder holder = null;
if(convertView==null){
int type = getItemViewType(position);
holder = new ViewHolder();
if(type == TYPE_ITEM){
convertView = mInflater.inflate(R.layout.item_layout,null);
holder.textView = (TextView) convertView.findViewById(R.id.text);
}
else{
convertView = mInflater.inflate(R.layout.category_layout,null);
holder.textView = (TextView) convertView.findViewById(R.id.category);
convertView.setClickable(false);
}
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(this.getItem(position));
return convertView;
}
private static class ViewHolder {
public TextView textView;
}
}
In your CustomAutoCompleteAdapter, override isEnabled() method to disable view type TYPE_CATEGORY.
Add below code in your CustomAutoCompleteAdapter :
#Override
public boolean isEnabled(int position)
{
return ((getItemViewType(position) != TYPE_CATEGORY));
}
Hope this will help~
Try to remove focus from view by setting your convertview and textview both "unfocusable()". like this:
convertView.setFocusable(false);
holder.textview.setFocusable(false);
Hope it'll work!
I made the following adapter which I used before to create rows that are the same all the time. I removed the creation of the textviews and imageviews.
What I want to achieve is to create different rows depending on the key.
A row could contain text and an image while another row only has text. How would I be able to do that?
public class DetailsListAdapter extends ArrayAdapter<ArrayList<String>> {
private Context context;
private ArrayList<String> keys;
public DetailsListAdapter(Context context, ArrayList<String> keys) {
super(context,R.layout.details);
this.context = context;
this.keys = keys;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.details, null);
return v;
}
#Override
public int getCount(){
return keys.size();
}
}
To inflate different layout for rows you need to override getViewItemType and getViewTypeCount.
You should have a look at the video in the link.
http://www.youtube.com/watch?v=wDBM6wVEO70
private static final int TYPE_ITEM1 = 0;
private static final int TYPE_ITEM2 = 1;
private static final int TYPE_ITEM3 = 2;
Then
int type;
#Override
public int getItemViewType(int position) {
if (position== 0){
type = TYPE_ITEM1;
} else if (position == 1){
type = TYPE_ITEM2;
}
else
{
type= TYPE_ITEM3 ;
}
return type;
}
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
LayoutInflater inflater = null;
int type = getItemViewType(position);
// instead of if else you can use a case
if (convertView == null) {
if (type == TYPE_ITEM1 {
//infalte layout of type1
convertView = mInflater.inflate(R.layout.layouttype1,
parent, false);
}
if (type == TYPE_ITEM2) {
//infalte layout of type2
convertView = mInflater.inflate(R.layout.layouttype2,
parent, false);
} else {
//infalte layout of normaltype
convertView = mInflater.inflate(R.layout.layouttype3,
parent, false);
}
...// rest of the code
return convertView;
}
override getViewTypeCount inside this function return the number of types views you want to use inside listview.
Override getItemViewType(int position) inside this write your logic to get the type of view.
#Override
public int getViewTypeCount() {
return 2; //return 2, in case you have two types of view
}
#Override
public int getItemViewType(int position) {
return (contition) ? R.layout.layout1 : R.layout.layout2; //return 2, in case you have two types of view
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = (convertView == null) ?View.inflate(context, getItemViewType(position), null) : convertView;
return convertView;
}
I have a custom listView in which i want to inflate three different layouts.
I have seen many questions but they are all for odd and even position but in my listView the layout that should be inflated to the listView depends on other condition and its dynamic like
if (i == 0) i get the first Layout to be inflated and if (i==1) second one and so on,
The variable "i" is equal to the value I would be getting from my main Activity.
public class SocialListAdapter extends ArrayAdapter<Item> {
private Activity activity;
private List<Item> items;
private Item objBean;
private int row;
private int i;
public SocialListAdapter(Activity act, int resource, List<Item> arrayList) {
super(act, resource, arrayList);
// TODO Auto-generated constructor stub
this.activity= act;
this.items = arrayList;
this.row = resource;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getCount() {
return items.size();
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder;
objBean = items.get(position);
i = objBean.getI();
if (view == null) {
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(i == 0){
view = inflater.inflate(R.layout.list, null);
} else if (i == 1){
view = inflater.inflate(R.layout.row1, null);
}
holder = new ViewHolder();
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
I tried this code but dint work...
I was getting an error when i scroll through the listView arrayoutofbondexception
getItemViewType has to return a number from 0 to getViewTypeCount.
Try this :
public class CustomAdapter extends BaseAdapter {
private ArrayList<String> comments;
Context mContext;
public CustomAdapter(Context context, ArrayList<String> comments) {
this.mContext = context;
this.comments = comments;
}
public View getView(final int position, View convertView, ViewGroup parent){
String item = comments.get(position);
if (getItemViewType(position) == 0) {
View v = convertView;
if (v == null) {
//GET View 1
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup viewGroup = (ViewGroup)inflater.inflate(R.layout.item_comment2, null);
v = viewGroup;
}
//Fill Data for Ist view
TextView comm = (TextView) v.findViewById(R.id.comment);
comm.setText(item);
return v;
} else if (getItemViewType(position) == 1) {
View v = convertView;
if (v == null) {
//GET View 2
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup viewGroup = (ViewGroup)inflater.inflate(R.layout.item_comment1, null);
v = viewGroup;
}
//Fill Data for IInd view
TextView comm = (TextView) v.findViewById(R.id.comment);
comm.setText(item);
return v;
} else {
//GET View 3
View v = convertView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup viewGroup = (ViewGroup)inflater.inflate(R.layout.item_comment3, null);
v = viewGroup;
}
//Fill Data for IInd view
TextView comm = (TextView) v.findViewById(R.id.comment);
comm.setText(item);
return v;
}
}
#Override
public int getCount() {
return comments.size();
}
#Override
public Object getItem(int position) {
return comments.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if(position == 0)
return 0;
else if (position == 1)
return 1;
else
return 2;
}
}
You can optimize and make your scrolling smooth by defining view holder :
Try this also
in getItemViewType you can set your condition where do you want to show which type of view.
use "if(position==0)" inside getView method
You can check the condition and inflate the layout that you want.
Here is the example. I have three category depending on it I inflate different layouts.
public class ApplicationAdapter extends BaseAdapter
{
private Context cntx;
/**
* 0 - featured 1- Top rated 2- other
*/
private int whichCategory;
/**
* Constructor
*
* #param context
* #param iwhichCategory
* 0 - featured 1- Top rated 2- other
*/
public ApplicationAdapter(Context context, int iwhichCategory)
{
cntx = context;
whichCategory = iwhichCategory;
}
#Override
public int getCount()
{
return entApplications.size();
}
#Override
public Object getItem(int position)
{
return null;
}
#Override
public long getItemId(int position)
{
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
// This a new view we inflate the new layout
LayoutInflater inflater = (LayoutInflater) cntx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (whichCategory == 0)
convertView = inflater.inflate(R.layout.featured_app_grid_item, parent, false);
if (whichCategory == 1)
convertView = inflater.inflate(R.layout.other_app_grid_item, parent, false);
if (whichCategory == 2)
convertView = inflater.inflate(R.layout.other_app_grid_item1, parent, false);
}
}
}
Im building an listview with sections. i was using this answer of a post and asked a question before, but am stuck again. I think it is a pretty weird error.
When i start my activity, i can see the list on the screen, just as i want it. But the moment i try to start scrolling the activity crashes. I thought i implemented everything the same way, but apparently im not.
My adapter:
public class DelftAdapter extends BaseAdapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_SECTION = 1;
private Activity activity;
private List<ListItem> listItems;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
private final int[] bgColors = new int[] { R.color.list_odd, R.color.list_even };
public DelftAdapter(Activity a, ArrayList<ListItem> li) {
activity = a;
listItems = li;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return listItems.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return listItems.get(position).isSection() ? TYPE_SECTION : TYPE_ITEM;
}
#Override
public int getViewTypeCount() {
return 2; // sectionheader and regular item
}
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
View vi=convertView;
final ListItem li = listItems.get(position);
ItemViewHolder itemHolder;
SectionViewHolder sectionHolder;
switch (type) {
case TYPE_SECTION: // is sectionheader
if (vi == null) { //convertview==null
sectionHolder = new SectionViewHolder();
vi = inflater.inflate(R.layout.sectionedlistitem, null);
vi.setOnClickListener(null);
vi.setOnLongClickListener(null);
vi.setLongClickable(false);
sectionHolder.title = (TextView) vi.findViewById(R.id.list_header_title);
}else{//convertview is not null
sectionHolder = (SectionViewHolder)vi.getTag();
}
SectionItem si = (SectionItem)li;
sectionHolder.title.setText(si.getTitle());
break;
case TYPE_ITEM:// no sectionheader
if (vi == null) { //convertview==null
itemHolder = new ItemViewHolder();
vi = inflater.inflate(R.layout.singlelistitem, null);
itemHolder.name=(TextView)vi.findViewById(R.id.tvname);
itemHolder.tip=(TextView)vi.findViewById(R.id.tvtip);
itemHolder.image=(ImageView)vi.findViewById(R.id.image);
}else{ // convertview != null
itemHolder = (ItemViewHolder)vi.getTag();
}
ListData ld = (ListData)li;
itemHolder.name.setText(ld.name);
itemHolder.tip.setText(ld.tip);
if (ld.photoUrl != null ){
imageLoader.DisplayImage(ld.photoUrl, itemHolder.image);
}else{
itemHolder.image.setImageURI(Uri.fromFile(new File("//assets/nopic.png")));
}
// alternating colors
int colorPos = position % bgColors.length;
vi.setBackgroundResource(bgColors[colorPos]);
break;
}
return vi;
}
public static class SectionViewHolder {
public TextView title;
}
public static class ItemViewHolder {
public TextView name;
public TextView tip;
public ImageView image;
}
}
I build two ViewHolders for the two different kind of views. The error that occurs is NullPointerException on the itemHolder.name.setText(ld.name); line.
The thing i don't get is that the code works for the first few entrys but fails when i start scrolling. In the data i'm using, name and tip are never empty, only photoUrl might be but that is covered in the code.
Anyone knows why this piece of code is failing?
In the code paths where you create a new viewholder and inflate a new view, you never actually store the viewHolder in the Views tag, so when you scroll and get an exisitng view, view.gettag() returns null, and later when you try and use the ViewHolder you get the Null Pointer Exception. You need to add the calls to setTag().