Custom list adapter repeats entries - android

I am trying to create a ListView that will be populated with the entries from an array.
So this is my item layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="60dip" >
<ImageView android:id="#+id/list_item_image"
android:layout_height="wrap_content"
android:padding="2dip"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_width="50dip"/>
<TextView android:id="#+id/list_item"
android:layout_height="fill_parent"
android:textSize="25sp"
android:layout_width="fill_parent"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:padding="5dip" >
</TextView>
</LinearLayout>
I tried changing the layout_height of the LinearLayout but I ran into some problems. If I keep the height at wrap_content, my list is displayed with the correct entries -- Item 1, Item 2, Item 3, and so on until Item 12. However if I change the height to 60dip, the entries repeat after the sixth entry (I get Item 1, Item 2, Item 3, Item 4, Item 5, Item 6, Item 1, Item 2, Item 3...). If I keep on making it larger, the entries repeat more frequently.
This is a snippet from the ListAdapter where I set the list entries:
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout;
if (convertView == null){
layout = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.items_list_item, parent, false);
TextView title = (TextView) layout.findViewById(R.id.list_item);
title.setText(menuItems[position]);
ImageView icon = (ImageView) layout.findViewById(R.id.list_item_image);
int logo = getResources().getIdentifier(menuIcons[position], "drawable", getPackageName());
icon.setImageResource(logo);
} else {
layout = (LinearLayout) convertView;
}
return layout;
}
Anybody else encountered this problem? I do not understand what is going on since I thought it should be straight-forward grabbing from the array.
EDIT: included the whole of my getView() method. Pardon the ugly way of getting the icons, I haven't figured it out yet,

You didn't post enough code, but in your Adapter's getView(...) try to make use of the convertView.
public View getView(int position, View convertView, ViewGroup parent){
if(convertView == null){
convertView = mInflater.inflate(R.layout.my_listitem_row, parent, false);
}
//...fill the TextViews on your layout
return convertView;
}
Fetching the icons should be as easy as
icon.setImageResource(R.drawable.my_icon); //the res/drawable folder has the my_icon.png file

From the little snippet of code I'm going to guess that it's something to do with the views being reused and the text not getting updated. I'm not certain though without seeing all of the code for the ListAdapter.
Take a look at this session from Google I/O 2010 for loads of really helpful information on how to use ListViews (and by extension adapters). It contains lots of tips and advice on the best way of using them. If you have time watch the video, if not the slides are avaliable.
Good Luck :)

Related

Android ArrayAdapter not properly converting Views

I have a custom ArrayAdapter for a ListView that uses a custom row layout, defined separately in XML. The layout is just three TextViews and an ImageView, put together in a RelativeLayout. To improve performance, the adapter uses a ViewHolder system like the one described here to convert existing Views instead of inflating new ones. In ArrayAdapter.getView(), the adapter is supposed to bold or unbold the first TextView, depending on a boolean.
When I first open the app, all of the TextViews are properly bolded or unbolded. However, if I scroll to the bottom of the ListView, then scroll back to the top, all of the title TextViews are bold, even if they aren't supposed to be. I think it must have something to do with converting existing views that are already bold, but I can't figure out what it is. I've debugged the app with Android Studio, and it runs just like I think it should -- when I scroll back up, the adapter properly bolds/unbolds things in the debug window, but they all seem to be bold on the app.
One thing I have noticed is that if I change the textStyle attribute of the TextView to "bold," all the title TextViews are bold from the beginning, and never change. It's only if I remove textStyle or set it to "normal" that the TextViews are normal at the start.
Here's getView() in the ArrayAdapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
PostShell shell = postShellList.get(getCount() - 1 - position); //I stack my ListView backwards, so index 0 is at the bottom
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_layout, parent, false);
holder = new ViewHolder();
holder.firstLine = (TextView) convertView.findViewById(R.id.firstLine);
holder.secondLine = (TextView) convertView.findViewById(R.id.secondLine);
holder.thirdLine = (TextView) convertView.findViewById(R.id.thirdLine);
holder.image = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.firstLine.setText(shell.postTitle);
if (shell.unread) {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.BOLD);
} else {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.NORMAL);
}
//convert other TextViews
}
My ViewHolder class is just a static class with a few TextViews and an ImageView.
And here's the relevant part of the code for the row layout I'm using:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="88dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="84dp"
android:paddingRight="16dp"
android:paddingTop="6dp"
android:id="#+id/firstLine"
android:ellipsize="end"
android:text="First"
android:textStyle="normal"
android:singleLine="true" />
<!-- other views -->
</RelativeLayout>
The problem seems to be that "unboldening" the text does not work with the following statement:
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.NORMAL);
The following snippet leaves out the holder.firstLine.getTypeface() and just uses a simpler variety of setTypeface(). Worked for me.
if (shell.unread) {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.BOLD);
} else {
holder.firstLine.setTypeface(Typeface.DEFAULT);
}
PostShell shell = postShellList.get(getCount() - 1 - position); //I stack my ListView backwards, so index 0 is at the bottom
If the ListView asks for the element at a particular position, you'd better give it the element at that position or it's going to be confused. IF you want to alter the order of the items in the list, change the order of the list, don't try to trick ListView into rendering it in a different order.
You should store your views in the normal order and use android:stackFromBottom or setStackFromBottom().

why my ListView never calls onItemClick?

I have really searched a lot on google, stack overflow , and found this : OnItemCLickListener not working in listview ANDROID. But seems sunshine's answer not works for my case. Other answers are all similar ones.
I have tried the following approaches:
add android:focusable="false" to my list item xml
add TextView.setFocusable(false) and TextView.setClickable(false) in ViewHolder
using the xml as described in the above link.
But none of them work.
Here's my xml and java code:
list_item.xml:
<?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="wrap_content"
android:baselineAligned="false"
android:descendantFocusability="blocksDescendants"
android:focusable="false"
android:paddingTop="2dp"
android:gravity="center_vertical" >
<TextView
android:id="#+id/ninegrid_number_list_choice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="#dimen/ninegrid_number_listchoice_text_size"
android:gravity="center"
>
</TextView>
</LinearLayout>
getView int list adapter.java:
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
ViewHolder holder;
if (convertView == null ) {
convertView = mInflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.mTextView = (TextView)convertView.findViewById(R.id.ninegrid_number_list_choice);
holder.mTextView.setFocusable(false);
holder.mTextView.setClickable(false);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.mTextView.setText(mList.get(position));
holder.mTextView.setTextColor(mTextColor);
holder.mTextView.setFocusable(false);
holder.mTextView.setClickable(false);
return convertView;
}
Edit:
in my activity:
listchoice.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
listChoice.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
Log.v(tag, "sdf");
}
});
On method getView() of the adapter, you can set the onclick event of convertView before returning it.
That event handler can be treated as onItemClick :)
The listview click listener not working when we use buttons,imagebutton etc. As you use only textview so there will not be problem like that...
As you used linearlayout no need to use android:focusable="false" in your linear layout.
We used this code only during usage of buttons. Also no need to use code holder.mTextView.setClickable(false);
As text is not a button so it takes no any focus. When u clicked it will click on the list cell not on the textview...
So Simply inflate and then after setting custom adapter to the listview...
setonitemclicklistner to the list view....
UPDATED ANSWER
convertView.setOnClickListener(new OnItemClickListener( position ));
This will surely works...
I just ran into the same problem, and tried all suggested solutions but none worked for me. After trying out different things, I found out there was another reason for my problem. I was wrapping my list_item inside a ScrollView, which was causing my onItemClick to not be called. I hope this helps someone.
One thing that seems to override the row clicks is if you have textviews, make sure they don't have an android:inputtype associated with them
remove the inputtype and you can click the row
This is in addition of course to the above answers on

ListView with Title

Is there a way to insert another View above the List in a ListView? For example, I want a Title Bar (TextView) that sits on top of the List, and scrolls out of view as the List scrolls. I can think of two ways so far of doing this, but both are hacks.
Idea #1 - Use a LinearLayout that pretends to be a ListView. But the problem is you are unable to take advantage of the "smart" loading/unloading of views that an adapter provides.
<ScrollView>
<LinearLayout>
<TextView/>
<LinearLayout
android:orientation="vertical" /> # add ListItems here
</LinearLayout>
</ScrollView>
Idea #2 - Use a hacky ArrayAdapter, with a getView method similar to this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position == 0)
View view = inflater.inflate(R.layout.special_list_item, null);
else
View view = inflater.inflate(R.layout.regular_list_item, null);
...
return vi;
}
Simple. Just add a header view to your ListView
TextView textView = new TextView(context);
textView.setText("Hello. I'm a header view");
listView.addHeaderView(textView);

Double AddView on adapter

I have made linear layout and add view on it, however, the view appear twice, I dont know why it happen.Can anyone fix it??
I have problem about the adapter and I look few time and I find no strange here. But I delete the statement of addView it will not appear any View I have added before.
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if(convertView == null)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
// inflater.inflate(R.layout. parent,false);
convertView = inflater.inflate(R.layout.exerciseui_item,parent,false);
}
Exercise question = exercises.get(position);
TextView question_view = (TextView) convertView.findViewById(R.id.exercise_question);
String question_test = question.getOrder() + " " + question.getText() ;
question_view.setText(question_test);
int answer_num = question.getAnswer().size();
LinearLayout linear = (LinearLayout) convertView.findViewById(R.id.exercise_answer);
ExerciseAnswer answer = question.getAnswer().get(0);
int answer_order = answer.getOrder();
String answer_text = answer.getText();
String answer_final = answer_order + " " + answer_text;
TextView answer_view = new TextView(linear.getContext());
answer_view.setPadding(20, 5, 5, 5);
answer_view.setTextSize(30);
answer_view.setText(answer_final);
linear.addView(answer_view);
return convertView;
}
The following is the xml of the exerciseui_item
<?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:orientation="vertical"
android:id="#+id/exercise_answer" >
<TextView
android:id="#+id/exercise_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
></TextView>
</LinearLayout>
It sounds like view re-cycling might be behind this. Your adapter is responsible for creating the view of each item in the data set (that is the purpose of the getView method). Android, in order to conserve resources, will re-cycle views. That is why the getView method is passed a View. If the passed-in view is non-null, that means it has already been used to display data in this list.
Thus it is the responsibility of the adapter, to "clean up" the view. In your case, that means that you have to account for the fact that you may have already dynamically added the TextView element to this view (in a previous getView call). Failure to "clean up" your view means that each time a view is re-cycled, your method will be adding yet another TextView to the layout.
In your case, I would suggest searching the LinearLayout for the answer TextEdit. (Give this TextEdit an id so that you can find it by using findViewById()). If it already exists, then you do not need to add it.
An other approach would be to include the 2nd TextEdit right in your XML layout. It is not clear to me why this needs to be added dynamically.

Why does my CheckedTextView icon jump around to different elements in my GridView?

I have a GridView whose elements are based on a FrameLayout containing an ImageView and a CheckedTextView. The xml file for the FrameLayout is as follows:
<FrameLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:id="#+id/gridImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<ImageView
android:id="#+id/image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:adjustViewBounds="false">
</ImageView>
<CheckedTextView
android:id="#+id/imageTick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal|bottom"
android:checkMark="#drawable/icon"
android:checked="false"
android:visibility="invisible"
>
</CheckedTextView>
</FrameLayout>
EDIT: This is the adapter's getView() method i use for each element:
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("getView()", position+"");
View v;
ImageView imageView;
if(convertView == null) {
v = LayoutInflater.from(mContext).inflate(R.layout.photo_text_view, null);
v.setLayoutParams(new GridView.LayoutParams(100,100));
}
else
{
v = convertView;
}
imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));
v.setPadding(8, 8, 8, 8);
return v;
}
In my activity class, I load a context menu and once i select an option i make the CheckedTextView visible for that GridView element like so :
GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);
So two things can potentially happen from this point:
1) Lets say I picked the element at position 0, the CheckedTextView becomes visible, but when i scroll down my GridView, another element (e.g. position 17) has its CheckedTextView visible aswell. This continues on random elements as i scroll down towards the bottom.
2) If i pick an element towards the bottom, scroll back up to the top, and run one of methods to make all CheckedTextView's invisible, a NullPointerException is thrown for the element at the bottom. This happens at this point: View selected =gridView.getChildAt(position); where selected becomes null.
Whats going on here? Why do these random CheckedTextView's become visible and why do i get exceptions?
The problems lie in the way you track checked items:
GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);
1) getChildAt will not give you the correct view if you've scrolled into content and you're indexing by adapter position. When referencing an active view in a GridView or ListView you want to do something like this:
final int index = position - gridView.getFirstVisiblePosition();
View selected = gridView.getChildAt(index);
The reason is that your GridView only keeps child views for adapter items that it is currently displaying. It may only have child views for elements 4 to 23 if elements 0 to 3 were scrolled off of the top earlier.
This is why you're getting exceptions when you do View selected =gridView.getChildAt(position); a view for that position does not actually exist when it's off-screen, so getChildAt returns null.
2) When a ListView or GridView has its content change or it otherwise needs to re-layout its child views, it does so by re-binding existing views using the convertView parameter to your adapter. Your adapter never adjusts the checked state of your item views, so a checked view can be reused for an item that should be unchecked later.
To solve this you should have your data model that your adapter presents track the checked state of your items rather than relying on the item views to do it. This means that your adapter should always verify/set the checked state of the item in getView. It may look something like this:
imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));
CheckedTextView checkView = (CheckedTextView) v.findViewById(R.id.imageTick);
if (mData.isItemChecked(position)) {
checkView.setChecked(true);
checkView.setVisibility(View.VISIBLE);
} else {
checkView.setVisibility(View.GONE);
}
Then when your option is selected that should change the checked state of an item, instead of the code snippet above do something like:
data.setItemChecked(position, true);
adapter.notifyDataSetChanged();
notifyDataSetChanged() will tell the GridView that it should re-bind the item views, which will have your getView method fix up the checked states properly.

Categories

Resources