Android Handling many EditText fields in a ListView

12,636

Solution 1

Not taking into account if this is a good UI design, here's how you'd do it:

public class TestList
{
    public void blah()
    {
        ArrayAdapter<DataBucket> listAdapter = new ArrayAdapter<DataBucket>()
        {

            @Override
            public View getView(int position, View convertView, ViewGroup parent)
            {
                if (convertView == null)
                {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
                }

                final DataBucket dataBucket = getItem(position);
                final EditText editText = (EditText) convertView.findViewById(R.id.theText);
                editText.setText(dataBucket.getSomeData());
                editText.addTextChangedListener(new TextWatcher()
                {
                    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
                    {

                    }

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

                    }

                    public void afterTextChanged(Editable editable)
                    {
                        dataBucket.setSomeData(editable.toString());
                    }
                });

                return convertView;
            }
        };
    }

    public static class DataBucket
    {
        private String someData;

        public String getSomeData()
        {
            return someData;
        }

        public void setSomeData(String someData)
        {
            this.someData = someData;
        }
    }
}

'DataBucket' is a placeholder. You need to use whatever class you created to store the data that gets put into and edited in the edit text. The TextWatcher will have a reference to the data object referenced. As you scroll, the edit text boxes should get updated with current data, and text changes should be saved. You may want to track which objects were changed by the user to make data/network updates more efficient.

* Edit *

To use an int position rather than directly referencing the object:

ArrayAdapter<DataBucket> listAdapter = new ArrayAdapter<DataBucket>()
{

    @Override
    public View getView(final int position, View convertView, ViewGroup parent)
    {
        if (convertView == null)
        {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
        }

        final DataBucket dataBucket = getItem(position);
        final EditText editText = (EditText) convertView.findViewById(R.id.theText);
        editText.setText(dataBucket.getSomeData());
        editText.addTextChangedListener(new TextWatcher()
        {
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
            {

            }

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

            }

            public void afterTextChanged(Editable editable)
            {
                getItem(position).setSomeData(editable.toString());
            }
        });

        return convertView;
    }
};

* Edit Again *

I feel compelled to say for posterity, I wouldn't actually code it this way. I'd guess you want a little more structured data than a String array, and you're maintaining the String array outside, as well as an ArrayAdapter, so its sort of a weird parallel situation. However, this will work fine.

I have my data in a single String array rather than a multi-dimensional array. The reason is because the data model backing the GridView is just a simple list. That may be counterintuitive, but that's the way it is. GridView should do the layout itself, and if left to its own devices, will populate the row with variable numbers of cells, depending on how much data you have and how wide your screen is (AFAIK).

Enough chat. The code:

public class TestList extends Activity
{
    private String[] guess;

    //Other methods in here, onCreate, etc

    //Call me from somewhere else. Probably onCreate.
    public void initList()
    {
        ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, /*some resourse id*/, guess)
        {

            @Override
            public View getView(final int position, View convertView, ViewGroup parent)
            {
                if (convertView == null)
                {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
                }

                final String theData = getItem(position);
                final EditText editText = (EditText) convertView.findViewById(R.id.theText);
                editText.setText(theData);
                editText.addTextChangedListener(
                        new MyTextWatcher(position)
                );

                return convertView;
            }
        };

        gridView.setAdapter(listAdapter);
    }

    class MyTextWatcher extends TextWatcher {
         private int position;

         public MyTextWatcher(int position) {
                 this.position = position;
         }

         public void afterTextChanged(Editable s) {
                 guess[position] = s.toString();
         }

     // other methods are created, but empty
    }
}

Solution 2

To track the row number, each listener in EditText has to keep a reference to an item in a list and use getPosition(item) to get the position in a ListView. My example uses Button but I think that it can be applied to EditText.

class DoubleAdapter extends ArrayAdapter<Double> {
    public DoubleAdapter(Context context, List<Double> list) {
        super(context, android.R.layout.simple_list_item_1, list);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_row, null);
        }

        // keep a reference to an item in a list
        final Double d = getItem(position);

        TextView lblId = (TextView) convertView.findViewById(R.id.lblId);
        lblId.setText(d.toString());

        Button button1 = (Button) convertView.findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // the button listener has a reference to an item in the list
                // so it can know its position in the ListView
                int i = getPosition(d);
                Toast.makeText(getContext(), "" + i, Toast.LENGTH_LONG).show();
                remove(d);
            }
        });

        return convertView;
    }
}

Solution 3

It might be worth considering whether you need the edit texts to be stored in the list cells? It seems a little bit unnecessary when the user will only be editing one at a time.

Whilst I do not know how your app is designed I would recommend rethinking your user experience slightly so that when an list item is pressed a single text edit appears for them to edit. That way you can just get the list items reference as you normally would with a list adapter, store it whilst the user is editing and update it when they have finished.

Solution 4

i'm not sure if that's a nice design you have, as the EditText content will have a good chance of having problems (shuffling content, missing text) once your listview is scrolled. consider trying out m6tt's idea.

but if you really want to go your way, can you post some code, specifically of your TextWatcher?

Share:
12,636
Rob
Author by

Rob

I am a freelance software developer with 5+ years of professional experience. I am based in Detroit, MI. If you're looking to develop an Android app that looks and works great on all devices, I can make that happen. I am also available for work on Classic ASP, ASP.NET, and MS SQL Server applications.

Updated on June 25, 2022

Comments

  • Rob
    Rob about 2 years

    Just a basic question: If I have several dozen EditText fields that are part of a ListAdapter, how can the individual EditText fields know to which row they belong?

    Currently I am using TextWatcher to listen for text input. I have tried extending TextWatcher so that I can pass in the position of the EditText to TextWatcher's constructor.

    However, when the soft keyboard pops up, the positions that correspond to the various EditText fields shuffle.

    How can I track the EditText fields to their proper position?

    I am using a GridView to lay things out. The layout of each item is an ImageView with a TextView and EditText field below it.

    The text for each EditText is held in a global String array called strings. It is initially empty, and is updated by my TextWatcher class.

    public void initList()
        {
            ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, R.layout.shape, strings)
            {
                @Override
                public View getView(final int position, View convertView, ViewGroup parent)  {
                    if (convertView == null)  {
                        convertView = LayoutInflater.from(getContext()).inflate(R.layout.shape, null);
                    }
                    final String theData = getItem(position);
                    final EditText editText = (EditText) convertView.findViewById(R.id.shape_edittext);
                    editText.setText(theData);
                    editText.addTextChangedListener(
                            new MyTextWatcher(position, editText)
                    );
    
                    ImageView image = (ImageView) convertView.findViewById(R.id.shape_image);
                    image.setBackgroundResource(images[position]);
    
                    TextView text = (TextView) convertView.findViewById(R.id.shape_text);
                    if (gameType == SHAPES_ABSTRACT)
                        text.setText("Seq:");
                    else
                        text.setVisibility(View.GONE);  
    
                    return convertView;
                }
    
                @Override
                public String getItem(int position) {        return strings[position];       }
            };
    
            grid.setAdapter(listAdapter);
        }
    
    
    private class MyTextWatcher implements TextWatcher {
        private int index;
        private EditText edittext;
        public MyTextWatcher(int index, EditText edittext) { 
                   this.index = index; 
                   this.edittext = edittext;    
            }
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
        public void onTextChanged(CharSequence s, int start, int before, int count) {}
        public void afterTextChanged(Editable s) {  strings[index] = s.toString();      }
        public void setIndex(int newindex) {  index = newindex;    }
    }
    

    When I click into the first EditText (see picture), the EditText shifts to the one under the smiley face.

    Shows how the EditText fields are layed out