5

Still new to android and even more to custom cursor adapter so I'm having trouble understanding how to prevent my listview from recycling views to prevent input from one edittext to show up in another when scrolled. I've seen on other post saying to change the name of convertview but how to do that I'm drawing a blank. I was hoping someone here would be able to give more details or example of how to do based of what code I've wrote so far.

public class editview extends ListActivity {
    private dbadapter mydbhelper;
    private PopupWindow pw;
    public static int editCount;
    public static ListView listView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mydbhelper = new dbadapter(this);
        mydbhelper.open();


        View footer = getLayoutInflater().inflate(R.layout.footer_layout, null);
        ListView listView = getListView();
        listView.addFooterView(footer);
        showResults();
        }

    //Populate view
    private void showResults (){
        Cursor cursor = mydbhelper.getUserWord();
        startManagingCursor(cursor);
        String[] from = new String[] {dbadapter.KEY_USERWORD};
         int[] to = new int[] {R.id.textType};
         ItemAdapter adapter = new ItemAdapter(this, R.layout.edit_row, cursor,
                        from, to);
            adapter.notifyDataSetChanged();
            this.setListAdapter(adapter);
            editCount = adapter.getCount();

    }


            //footer button
            public void onClick(View footer){
                    final MediaPlayer editClickSound = MediaPlayer.create(this, R.raw.button50);
                    editClickSound.start();
                    startActivity(new Intent("wanted.pro.madlibs.OUTPUT"));

                }

//custom cursor adapter
class ItemAdapter extends SimpleCursorAdapter {
    private LayoutInflater mInflater;
    private Cursor cursor;


    public ItemAdapter(Context context, int layout, Cursor cursor, String[] from,
            int[] to) {
        super(context, layout, cursor, from, to);
        this.cursor = cursor;
        mInflater = LayoutInflater.from(context);

    }


    static class ViewHolder {
        protected TextView text;
        protected EditText edittext;

    }

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


        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.edit_row, null);


             holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.textType);
            holder.edittext = (EditText) convertView.findViewById(R.id.editText);



            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();

        }
        cursor.moveToPosition(position);
        int label_index = cursor.getColumnIndex("userword"); 
        String label = cursor.getString(label_index);

        holder.text.setText(label);

        return convertView;

    }

}

Changed it to

class ItemAdapter extends SimpleCursorAdapter {
    private LayoutInflater mInflater;
    private Cursor cursor;
    Map<Integer, String> inputValues = new HashMap<Integer, String>();
    public View getView(final int position, View convertView, ViewGroup parent) {
        ....

        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.edit_row, null);


             holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.textType);
            holder.edittext = (EditText) convertView.findViewById(R.id.editText);


            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();

        }
        cursor.moveToPosition(position);
        int label_index = cursor.getColumnIndex("userword"); 
        String label = cursor.getString(label_index);

        holder.text.setText(label);
        String oldText =  inputValues.get(position);
        holder.edittext.setText(oldText == null ? "" : oldText); 
        holder.edittext.addTextChangedListener(new TextWatcher(){
            public void afterTextChanged(Editable editable) {
                inputValues.put(position, editable.toString());
            }

but it is recycling after all edittext have data. Tried using holder.edittext.setText(oldText) but same effect.

Start filling in fields

Finish filling in fields

Scroll up.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
maebe
  • 553
  • 1
  • 6
  • 18

2 Answers2

4

First of all, you really don't want to prevent a list view from recycling its views. View recycling is a huge optimization. For a lot of really good info on lists, see the google IO talk: http://www.youtube.com/watch?v=wDBM6wVEO70

That being said, you've correctly identified your problem: You have far fewer EditTexts than you do items in your list. As the you scroll through the list those EditTexts are recycled so you see the same input over and over again.

Basically what you need to do is save the input for your EditTexts in some datastructure (a HashMap if they will only edit a few values, maybe a List if they will be changing most of the values, either would work) that maps the position to the input. You can do this by adding a textChangedListener to your edit texts in getView:

@Override
public View getView(final int position, View convertView, ViewGroup parent){
    ...
    cursor.moveToPosition(position);
    int label_index = cursor.getColumnIndex("userword");
    String label = cursor.getString(label_index);

    holder.text.setText(label);

    //clear whatever text was there from some other position
    //and set it to whatever text the user edited for the current 
    //position if available
    String oldText = yourMapOfPositionsToValues.get(position);
    holder.setText(oldText == null ? "" : oldText); 

    //every time the user adds/removes a character from the edit text, save 
    //the current value of the edit text to retrieve later
    holder.edittext.addTextChangedListener(new TextWatcher(){
        @Override
        public void afterTextChanged(Editable editable) {
            yourMapOfPositionsToValues.put(position, editable.toString());
        }
        ....
    };

    return convertView;
}

Whenever your user is done editing, you can run through your datastructure and do whatever with those values.

Edit:

I changed onTextChanged to afterTextChanged because I've used that before and I know it works. Keep in mind that afterTextChanged is called every time a LETTER changes, not just after the user finishes typing a word. If the user types "dog" afterTextChanged will be called three times, first with 'd', then with 'do', then with 'dog'.

A HashMap is simple: Map yourMapOfPositionsToValues = new HashMap();

to add or update an item: yourMap.put(position, someText); to fetch an item: yourMap.get(position);

if hashmaps don't make sense, spend some time researching them. They are an incredibly important data structure.

Your TextWatcher implementation is incorrect. Your data structure should not belong to a single view, but rather the activity or your adapter. It appears to you that positions aren't stable because your List is owned by each view. The positions themselves are stable in that unless the underlying data changes the cursor will return the same data every time for the same position. However, the edit text is used for multiple different positions.

Create a hashmap as an instance variable I demonstrated above in the constructor of your adapter. Then add exactly the TextWatcher I wrote originally, no need for a named class, anonymous is simpler. Your code should work.

Sam Judd
  • 7,317
  • 1
  • 38
  • 38
  • I was playing around with aftertextchange but I found that it wasn't storing the values how I wanted it to. IE it would store only part of the string into different parts of the Array. I could type something like dog into the last edittext and when I would call positions in the ArrayList editTextList. It would look like pos1 dog pos2 do pos3 do. I didn't understand why it was only grabbing part of the edittext string. – maebe Feb 25 '12 at 05:38
  • Positions seemed unreliable to but maybe I was calling them wrong. When I was using a simplycursor adapter I changed the id of edittext via for loop and would use that loop to store and call them on the next activity. But still ran into recycling problems. I've looked at hashmaps off and on for the last two weeks but can't seem to find any good info explaining how to use them. Only bits of code here and there. – maebe Feb 25 '12 at 05:38
  • //TextWatcher, saves data to Array for later use. class watcher implements TextWatcher { private int position; public watcher(int position) { this.position = position; } public void afterTextChanged(Editable s) { Log.e(String.valueOf(position), "is the position int"); String text = s.toString(); editview.editTextList.add(position, text); Is what I came up with my textwatcher – maebe Feb 25 '12 at 05:39
  • Oh yeah and my listview will have 4-10 edittext but to make sure they aren't to small on screen most of the time the list will be scrollable. The idea is they will input for each one, then on next activity I will take their input and use it to replace chars in a string from my database using my regex. – maebe Feb 25 '12 at 05:42
  • Ty for the link. Started to watch a while back and forgot to finish. Trying it now and a little confused on holder.setText(oldText == null ? "" : oldText); Wasn't sure on what to setText() in viewholder should be. Tried holder.edittext.settext... appear to work when I first scrolled down but then if I scrolled back up it recycled input from the bottom to replace what I put in the top. Going to play around with it but any insight as to what should be included in settext() would help. ty thus far. – maebe Feb 25 '12 at 21:44
  • oldText == null ? "" : oldText is an example of a ternary. It is equivalent to: if(oldText == null){ holder.editText.setText("") }else {holder.editText.setText(oldText); } You can google ternary for more info. In your case the idea is to set the edit text to blank if the user hasn't previously edited the text at the given position or set the edit text to whatever the user had written if they had previously edited the text at the position. This should take care of recycled input appearing. I'm sorry my code had a typo, you want holder.edittext.setText() not holder.setText(). – Sam Judd Feb 25 '12 at 22:40
  • After research into ternary I see how its a short hand if statement. But still recycling the input in edittext. When I have a list that fits all items on screen and call position in next activity for data in the hash everything is in place. But if a scroll is required to finish filling in the fields. When calling a position that was scrolled off the screen it uses a recycled item. IE 0, 1 & 9 position equal the same item for the screen shots above. – maebe Feb 25 '12 at 23:35
1

The solution to this is removing the added textwatcher before setting the text. Otherwise, the previous textwatcher on that view will still be called along with the new textwatcher. Store the textwatcher as a tag on the EditText to keep track of it.

Object oldWatcher = viewHolder.quantitySold.getTag();
    if(oldWatcher != null){
        viewHolder.quantitySold.removeTextChangedListener((CustomTextWatcher)oldWatcher);
    } 
    String oldText =  inputValues.get("key"+position);
    Log.d(TAG, "oldText: "+oldText+" position: "+position);
    viewHolder.quantitySold.setText(oldText == null ? "" : oldText);
    CustomTextWatcher watcher = new CustomTextWatcher(
            cursor.getString(SKUFragment.COL_NAME),
            cursor.getInt(SKUFragment.COL_ID),
            cursor.getDouble(SKUFragment.COL_UNIT_PRICE),
            position
    ) {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            if (s != null) {
                int quantity = 0;
                if (!TextUtils.isEmpty(s.toString())) {
                    quantity = Integer.parseInt(s.toString());
                    inputValues.put("key"+mPosition, "" + quantity);
                }else{
                    inputValues.put("key"+mPosition, "");
                }
                double value = quantity * skuPrice;
                mListener.onQuantityChanged(skuName+", position: "+mPosition, skuId, quantity, value);
            }
        }
    };
    viewHolder.quantitySold.setTag(watcher);
    viewHolder.quantitySold.addTextChangedListener(watcher);
SimuMax
  • 11
  • 1