12

I have list item with EditText in it, I dont know how many items there will be. I have a problem when I enter some text in EditText, and then scroll down a ListView, after I've scroll up again there is no text in my first EditText, or there is some text from other EditText from ListView.

I've tried TextWatcher, and saving data to array, but problems is that returned position of view in ListView isnt always right, so I lost some data from array. -.-

How to detect correct position of view in ListView?

For example:

If I have 10 items in ListView, and only 5 of them are currently visible. Adapter return position from 0 to 4...thats ok. When I scroll down position of item 6 is 0...wtf? and i lose data from array on position 0 :)

Im using ArrayAdapter.

Please help.

Here's some code:

public View getView(int position, View convertView, ViewGroup parent) {

    tmp_position = position;

    if (convertView == null) {

        holder = new ViewHolder();

        LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.element_in_game, null);

        holder.scoreToUpdate = (EditText) convertView
                .findViewById(R.id.elementUpdateScore);

        holder.scoreToUpdate.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                scoresToUpdate[tmp_position] = s.toString();
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start,
                    int count, int after) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        initScoresToUpdateEditTexts(holder.scoreToUpdate, hint);

        convertView.setTag(holder);

    } else {

        holder = (ViewHolder) convertView.getTag();

        holder.scoreToUpdate.setText(scoresToUpdate[tmp_position]);

    }

    return convertView;
}
tidbeck
  • 2,363
  • 24
  • 35
Veljko
  • 1,893
  • 6
  • 28
  • 58

10 Answers10

16

you should try in this way as below:

if (convertView == null) {

        holder = new ViewHolder();

        LayoutInflater vi = (LayoutInflater)    
        getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.element_in_game, null);

        holder.scoreToUpdate = (EditText) convertView
                .findViewById(R.id.elementUpdateScore);


        convertView.setTag(holder);

    } else {

        holder = (ViewHolder) convertView.getTag();

    }
   //binding data from array list
   holder.scoreToUpdate.setText(scoresToUpdate[position]);
   holder.scoreToUpdate.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                //setting data to array, when changed
                scoresToUpdate[position] = s.toString();
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start,
                    int count, int after) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

    return convertView;
}

Explanation: Recycling behavior of ListView, actually erase all data bind with its Row Item, when go out of vision. So every new row coming in in vision from whatever direction you need to bind data again. Also, when EditText text change you have to keep values also in some data structure, so later on again row data binding you can extract data for position. You can use, array ArrayList for keeping edittext data, and also can use to bind data.

Similar behavior use in Recyclerview Adapter, behind logic you need to know about data binding of row data of adapters.

Anand Tiwari
  • 1,583
  • 10
  • 22
  • Anand, hi, i am having the same issue right now, i tried the solution in your code but apparently i can't get it to work, how can i update specific data in arraylist depending on edittext – Pankaj Nimgade Jul 09 '15 at 05:47
  • @PankajNimgade, for me this works: http://stackoverflow.com/a/21404201/2914140. Inside `holder.scoreToUpdate.setOnFocusChangeListener(...` write: if (!hasFocus) { scoresToUpdate[position] = holder.scoreToUpdate.getText().toString(); } – CoolMind Aug 30 '16 at 21:15
  • updated answer for applying updated values from edittext on updating, here in place of array, we can also use arraylist or bean(if implementation using custom data type), I am not sure if setOnFocusChangeListener will work, suppose if user not change focus (while scroll) after input in edittext. Does onFocusChange() called before view recycled?. – Anand Tiwari Aug 31 '16 at 13:45
  • 1
    Be careful with this solution. The reasoning behind the recycling behavior is correct but the solution is wrong, because the textwatcher will modify the wrong `position` index once there are enough rows to recycle. – Merlevede Sep 24 '16 at 00:53
12

you can use setOnFocusChangeListener instead. in my case i have expandable List and it seems good :) like this :

holder.count.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus) {
                EditText et =(EditText)v.findViewById(R.id.txtCount);
                myList.get(parentPos).put(childPos,
                        et.getText().toString().trim());
            }
        }
    });
amin10043
  • 234
  • 3
  • 7
5

I had a related issue that I solved. Each row of my ListView has a different View (it contains different controls. EditView, ImageView, TextView). I want the data entered or selected in the those controls to persist even when the control is scrolled off the screen. The solution I used was to implement the following methods in ArrayAdapter like this:

public int getViewTypeCount() {
    return getCount();
}

public int getItemViewType(int position) {
    return position;
}

This will make sure that when getView() gets called it passes the same View instance that was returned from getView() on the first call.

public View getView(int position, View convertView, ViewGroup parent) {

  if (convertView != null)
    return convertView;
  else
  // create new view
}
bickster
  • 1,292
  • 17
  • 18
  • 1
    This is a very bad hack. By setting **getViewTypeCount** to **getCount**, you create a new row for every item in your ListView and recylcing doesn't happen, which can be a problem if you have alot of items or an old phone. – Marko Dec 07 '15 at 12:17
5

If you will only have ~10 rows, don't bother with the ListView. Just put them in a vertical LinearLayout and wrap that in a ScrollView, and it will save you some headache.

If you are going to have dozens or hundreds of rows, I suggest that you come up with a better UX paradigm than EditText widgets in ListView rows.

All that being said, it feels like you are not handling your row recycling properly, or are unaware that rows get recycled. If you have 10 items in your ListAdapter, and you only have room to display 5 rows with EditText widgets, you should not wind up with 10 EditText widgets when the user scrolls to the bottom. You should wind up with 5-7 -- the ones on the screen, and perhaps another one or two for recycling when the user scrolls next.

This free excerpt from one of my books goes through the process of creating custom subclasses of ArrayAdapter and getting the recycling working. It also covers having an interactive row, using a RatingBar for user input. That is far easier than an EditText, because all you have to worry about are click events. You are welcome to try to expand upon that technique with EditText widgets and TextWatcher listeners, but I'm not a fan.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • tnx for quick answer! i'll take a look at your book...hope that i'll come up with something :) – Veljko Jan 07 '12 at 21:33
2

I just recently was looking for a similar solution and found that using the textChangeListener NOT to the best approach. I answered a related question here:

https://stackoverflow.com/a/13312282/1812518

It is my first post, but I hope it is helpful enough.

Community
  • 1
  • 1
defpillz
  • 111
  • 1
  • 4
1

From a brief skim of your code, it looks like you're butting up against the recycler. When you scroll from position 1 to position 20 in a listview, it doesn't create 20 different instances of the listview item's view. Instead it has 7 or 8- When one scrolls off the screen, it gets added to the recycler, and then sent to the getView method as the convertView parameter, so you can re-populate it with new data instead of wastefully creating a new view.

Traditionally one uses the ViewHolder pattern to hold the subviews of a list item (textview, imageview, etc), not data (setting and getting the score from within the holder) - What's happening is that you set the value to zero at position zero, scroll down to the 6th item on a 5-item screen, the 0th item gets re-used for position 6, and it pulls the existing value from the holder attached to that listitem (in this case, a 0)

Simple answer: Don't store position-specific data in the holder! Stash it in an external array or hashtable somewhere.

Alexander Lucas
  • 22,171
  • 3
  • 46
  • 43
0

Get ride of the convertView == null check and it's else statement. Your going to get worse performance but it'll disable the recycling.

Marko
  • 20,385
  • 13
  • 48
  • 64
MinceMan
  • 7,483
  • 3
  • 38
  • 40
0

My adapter extends the BaseAdapter and I had a similar situation as bickster had where I had list view which had different views in them (a dynamic form like situation) and needed to persist data. My list view was not going to be too big, so recycling the view was not going to be a big resource consume in my case and hence a simple

if (convertView != null) return convertView;

at the start of the getView was sufficient for me. Not sure how efficient a solution it is, but it was what I was looking for.

Important Edit: This is ok if and only if you have very few rows (no scrolling), otherwise this hack is horrible one. You should instead take care of the data manually

Community
  • 1
  • 1
Neil B
  • 116
  • 1
  • 5
0

First, declare a String[] or List<String> member variable (inside the class outside the getView()) for storing the input text from your EditText object, and record the text in addTextChangeListener() you set on the EditText object.

    viewHolder.editText.removeTextChangedListener(viewHolder.textWatcher);

    viewHolder.textWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void afterTextChanged(Editable editable) {
            mInputs[position] = editable.toString();            //record input

            viewHolder.editText.setSelection(viewHolder.editText.getText().length());       //set cursor to the end
        }
    };

    viewHolder.editText.addTextChangedListener(viewHolder.textWatcher);
    viewHolder.editText.setText(mInputs[position]);

private class ViewHolder {
    EditText editText;
    TextWatcher textWatcher;

    public ViewHolder(View itemView) {
        editText = itemView.findViewById(R.id.main_editText);
    }
}
Sam Chen
  • 7,597
  • 2
  • 40
  • 73
0

A better way is Creating EditText at run time and setting its id as the position argument

of getView() method. so now when text is added, store it in a Vector variable (class

variable), and in the getView method based on position variable set the Text (Which you can

get using elementAt() method of vector) of corresponding EditText.

and don't forget to add this EditText to the inflated View.

ngesh
  • 13,398
  • 4
  • 44
  • 60
  • i've tried that earlier...but the problem is that getView returns position only in range of visible items. i think i mention it in my question. – Veljko Jan 17 '12 at 09:34
  • @Veljko.. so whats the problem in that. Any way you want to display text of visible EditText only. thats why i told you to maintain a Vector which stores the Text. or a Hashtable is a better option since you can give position as a Key by converting it to String. – ngesh Jan 17 '12 at 09:49