Double AddView on adapter - android

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.

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().

getView called several times even with layout_height="fill_parent"

I know there are already plenty of questions when it comes to getview() but I really can't figure out what's going on here.
I have an adapter where I dynamically add textViews :
public View getView(int position, View convertView, ViewGroup parent) {
OrderForAdapter currentData = data.get(position); // getting data
if (convertView == null) {
convertView = inflater.inflate(R.layout.order_detailsforlist, null);
}
if (currentData != null) {
LinearLayout order_details_layout =
(LinearLayout) convertView.findViewById(R.id.order_details_layout);
// Adding the textView name of the person
TextView labelName = new TextView(context);
labelName.setText(currentData.getName());
order_details_layout.addView(labelName);
// looping through all "titres" and display all the titles
ArrayList<ObjectForAdapter> listOfObjects = currentData.getObjects();
for (int i = 0; i < listOfObjects.size(); i++) {
TextView labelTitle = new TextView(context);
labelTitle.setText(listOfObjects.get(i).getTitle());
// Adding the title of each object
order_details_layout.addView(labelTitle);
}
}
return convertView;
}
XML :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.greederz.activities.ListOrdersActivity">
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#android:id/list" />
</LinearLayout>
When I first start the app, there is no problem, everything is displayed correctly and getView() is only called once.
However when I go to another activity and come back to this one, suddently getView is called 2 or 3 times in a row.
I also noticed that rotating the screen makes getView() being called once (so that works). So it must be something about the view being reused or the activity being not destroyed correctly.
The only answer I could find was layout_height="fill_parent" and i've changed it but no luck.
Please help :)

ListView: how to access Item's elements programmatically from outside?

I have the following situation.
I have a ListView, each item of the ListView is comprised of different widgets (TextViews, ImageViews, etc...) inflated form a Layout in the getView() method of the custom adapter.
Now, I would like to achieve the following:
when a certain event is triggered I want to change the background of a View which is inside the item.
Please how do I do it?
This is the the Item Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cardlayout"
android:layout_width="320dp"
android:layout_height="130dp"
android:background="#android:color/transparent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp" >
<FrameLayout
android:layout_width="320dp"
android:layout_height="117dp" >
<View
android:id="#+id/card"
android:layout_width="320dp"
android:layout_height="117dp"
android:background="#drawable/card_selector" />
</FrameLayout>
</LinearLayout>
I need to change the background of card
I have tried doing this:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
But no success :-((
When your event is triggered you should just call a notifyDataSetChanged on your adapter so that it will call again getView for all your visible elements.
Your getView method should take into account that some elements may have different background colors (and not forget to set it to normal color if the element doesn't need the changed background, else with recycling you would have many elements with changed background when you scroll)
edit :
I would try something like this :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.card, parent, false);
}
//This part should also be optimised with a ViewHolder
//because findViewById is a costly operation, but that's not the point of this example
CardView cardView =(CardView)convertView .findViewById(R.id.card);
//I suppose your card should be determined by your adapter, not a new one each time
Card card = getItem(position);
//here you should check sthg like the position presence in a map or a special state of your card object
if(mapCardWithSpecialBackground.contains(position))
{
card.setBackgroundResource(specialBackground);
}
else
{
card.setBackgroundResource(normalBackground);
}
cardView.setCard(card);
return convertView;
}
And on the special event i would add the position of the item into the map and call notifyDataSetChanged.
Use the onitemclicklistener which has method onclicksomething..that takes four or five parameters. (View parent, View view, int position, int id). Use the view parameter to customize your background.
Update
Here's some of my code, If you don't understand I recommend to read about recycling and ViewHolder pattern.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
{
ViewHolder viewHolder;
// If convertView isn't a recycled view, create a new.
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_gallery_frame, parent, false);
viewHolder = new ViewHolder();
// Here you must be able to find your Widget inside convertView and set a listener to it I guess?
viewHolder.nameHolder = (TextView) convertView.findViewById(R.id.nameTv);
// Set a reference to newly inflated view
convertView.setTag(viewHolder);
}
// If it is, then get the ViewHolder by tag
else{
viewHolder = (ViewHolder)convertView.getTag();
}
// Set the data
GalleryFrame galleryFrame = galleryFrameArrayList.get(position);
viewHolder.nameHolder.setText(galleryFrame.getName());
return convertView;
}
}
// Viewholder pattern which holds all widgets used
public static class ViewHolder{
public TextView nameHolder;
}
I assume you have a model object that you use to "draw" the list item , and for example the background color is determined based on a boolean or something.
All you need to do, is change the value on which you base your decision which background color should that TextView have.
Your getView() method should have code like that
if (myModelObj.isBrown()) {
myTextView.setBackgroundResource(R.drawable.brown_bg);
else
myTextView.setBackgroundResource(R.drawable.not_brown_bg);
All you should do when ur event is triggered, is set the value of the brown boolean in your model
and call notifyDataSetChanged() on your adapter
EDIT
If for some reason you don't wanna call nofitfyDataSetChanged(), althought it won't move the scroll position of your list and with the right recyclying it won't cause bad performance
You can find the View object that represent the list item you want to edit-if it's visisble-, and simply change the background in it, without refreshing the list at all.
int wantedPosition = 10; // Whatever position you're looking for
int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount();
int wantedChild = wantedPosition - firstPosition
if (wantedChild < 0 || wantedChild >= listView.getChildCount()) {
// Wanted item isn't displayed
return;
}
View wantedView = listView.getChildAt(wantedChild);
then use wantedView to edit your background
This answer can be found here
try this one:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
card.invalidate();
v.invalidate();
those function force your views to redraw itself and they will render again.
look at invalidate()
What I normally do is this:
public static class EventDetailsRenderer {
private TextView title;
private TextView description;
private Event item;
public EventDetailsRenderer(View view) {
extractFromView(view);
}
private final void extractFromView(View view) {
title = (TextView) view.findViewById(R.id.EventTitle);
description = (TextView) view.findViewById(R.id.Description);
}
public final void render() {
render(item);
}
public final void render(Event item) {
this.item= item;
title.setText(item.getTitle());
description.setText(item.getDescription());
}
}
private class EventsAdapter
extends ArrayAdapter<Event> {
public EventsAdapter(Context context) {
super(context, R.layout.list_node__event_details, 0);
}
public void addAllItems(Event... services) {
for (int i = 0; i < services.length; i++) {
add(services[i]);
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Event event = getItem(position);
EventDetailsRenderer eventRenderer;
if (convertView != null && convertView.getTag() != null) {
eventRenderer = (EventDetailsRenderer) convertView.getTag();
} else {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_node__event_details, null);
eventRenderer = new EventDetailsRenderer(convertView);
convertView.setTag(eventRenderer);
}
eventRenderer.render(event);
return convertView;
}
}
NOTE: that this example might not compile I pasted it from some code I have and deleted some lines to show an example but the logic it the same.
And then when you want to render it, just get the children from the list, iterate over them, check if the renderer contains the card you want to flip and call its render method... then you render a specific item in the list without effecting the rest of the items.
Let me know if this works...
Adam.
User EasyListViewAdapters library https://github.com/birajpatel/EasyListViewAdapters
Features
Easier than implementing your own Adapter (ie handling
BaseAdaper#getView).Very Easier to provide multi-row support.
Library takes care of recycling all views, that ensures performance
& helps your list view scroll smoothly.
Cleaner code. By keeping different RowViewSetter classes for
different row-types makes your code easy to manage & easy to reuse.
No data browsing, Library takes care of browsing data through
data-structure when View is being drawn or event occurs so that
Users does not have to look for their data to take actions.
Just by passing correct row-types library will Auto-map your
data-types to row-types to render views. Row views can be created by
using XML or Java (doesn't restrict to XML-Only Approach).
Load More callbacks can be registered to implement paginatation
support to your list.
Handling children viewclicks, you can also register for
Children(present inside your rows) view click events.
All these Views are registered with single OnClickListner so that
this mechanism is very memory efficient when click event occurs
users you gets clickedChildView, rowData,int eventId as callback
params.

ListView and rows recycling problem

I have this ListView:
I'm using a custom adapter.
As you see, each row is made of a checkbox, a big TextView, and a little TextView.
All items have defined the little TextView, even the "Item 2" but it's a void string.
The problem comes when I tap the EditText placed in the header of the list:
The keyboard appears, and the rows are recycled, so the getView method of my adapter is called. In that method I have an if clause where I check if the length of the "optional" text (the little TextView) is greater than 0. In that case I make some room (the space that you can see in the screenshot) and I display it.
The problem is that "Item 2" has the "optional" text initialized but it's void (0-sized). I don't understand why the if clause is executed. But more strangely... the else is also executed! In the else I just show a void string in the little TextView.
Why is this happening? The app is really simple. This is my getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListItem list_item = items.get(position);
TextView item_title = (TextView) convertView.findViewById(R.id.item_title);
TextView item_optional = (TextView) convertView.findViewById(R.id.item_optional);
item_title.setText(list_item.getTitle());
// If the task has an optional text, make some room and display it
if (list_item.hasOptional()) {
// This portion of code will be executed when you tap the EditText and the keyboard appears, putting the item up in the row
LayoutParams layout_params = (LayoutParams) item_title.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_title.setLayoutParams(layout_params);
item_optional.setText(list_item.getOptional());
item_optional.setVisibility(0);
} else {
// This portion of code will ALSO be executed when you tap the EditText... why? this should not happen!
item_optional.setText("");
}
return convertView;
}
The source code can be seen here (github).
When you modify a recycled view you have no idea what the state of the view is, with respect to how it might have been customized by previous calls to getView. The view you are recycling is not a fresh-out-the-box inflation of R.layout.list_row. Think of it as a "second hand" or "used" view.
So I can see under if (list_item.hasOptional().. you make some modification to the item_title.getLayoutParams(). As a view created here may later be recycled for a list item that will fail the check if (list_item.hasOptional() under the else code block you must reset the values you modify to the default specified in the layout.

Custom list adapter repeats entries

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 :)

Categories

Resources