Android Handling many EditText fields in a ListView
Asked Answered
M

5

6

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

Marathon answered 13/11, 2011 at 23:54 Comment(0)
L
5

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
    }
}
Luganda answered 14/11, 2011 at 0:37 Comment(13)
would it be simpler to just use tags or ids to identify the EditTexts? I don't know how the implementation would work, but could it be done in a simple manner?Marathon
I'm not sure this isn't simple, but I'll take a look.Luganda
Uses the position value rather than a direct reference to the object. If you mean simpler as in not using inner anonymous Java classes, I'd argue you should get used to the "complexity" ;)Luganda
It's complex in the sense that I honestly don't have a clue what your code is doing, I'm new to listadapters in general. To me, simple would mean assigning a number to the EditText object itself, either through a tag/id (whatever those are), or extending the EditText class to hold an extra variable corresponding to the row it is in. Either of those viable?Marathon
Like I said, you should get used to these constructs. Conceptually, they're simpler than what you're talking about, once you're used to them, and you'll see this kind of thing all over the place in Android. I'm not really sure what it is that you want to do, but extending EditText as an alternative to what I'm doing sounds very complex to me. Would not recommend.Luganda
Let me take a stab at explaining. 'DataBucket' is just something I made up. I don't know what type of data you have in your list. Perhaps post your code and I can comment. The code above does the following. It creates a new ArrayAdapter, and overrides 'getView' inline. That method gets called with 'position'. In there we get the data and EditText for position. The secret sauce is the TextWatcher. We create a TextWatcher inline, and reference 'position' in afterTextChanged. Not sure how to explain it more than that. Would need concrete code.Luganda
I just added some more details and some code, along with a picture of what I'm working with. The number of rows and columns of gridview items could vary from 3 to 100. Perhaps you could explain things with that context in mind? Does setting the TextWatcher right there in getView() function any differently than attaching my extended version of TextWatcher?Marathon
Could I simply add another method to my TextWatcher that allows for the resetting of the private variables row and col? In my getView() method then I would simply reset the values of the TextWatcher. Would that work (will try in 5 hours when I get home if I don't hear from you by then!).Marathon
The data backing a GridView is still a ListAdapter. To have any control over row/col values, you'd need to specify the number of columns on the GridView, then basically "assume" which values got put into which rows/cols. There's probably a way to do this dynamically, but its going to be ugly. The data is in a ListAdapter, so I'd not fight that and try to use it as is. Access values by the singular int 'position' value. Your keeping data in arrays of Strings? That complicates it somewhat. Let me think...Luganda
At any given time I know how many rows and columns there are in the grid. So given a position p, row = (int) p / numCols , and col = (int) p % numCols. I then pass the row and col to TextWatcher. The string array is a global variable in my activity.Marathon
OK. I guess I'd follow that up with, does it matter how GridView displayed the data? Its still backed by a single list. You could do it your way, but I feel like you're adding complexity that doesn't enhance the app in any way.Luganda
I have my reasons, but I can make the switch. In the meantime I posted more code at #8130757 and I'd be happy to award you with the correct answer on both if you want to take a crack at it over there.Marathon
If you take a look at the code above it now mimics your answer quite closely. The text still shifts around if I scroll violently.Marathon
V
3

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;
    }
}
Vivian answered 14/11, 2011 at 2:16 Comment(9)
What happens if the list element isn't a Double? What happens if more than one element has the same value? I think the goal here was to update values in the list, not remove them, right? This code would be problematic, unless the data was Double, and you were sure no two values would be the same. I don't think we know that.Luganda
If the list element is not a Double, then replace all Double in the code with the type of the element. And, you can update the value of an element via the reference d.Vivian
What if you have two (or more) elements with the same value?Luganda
If they are the same object, then both will be changed. If not, only one element will be changed.Vivian
Right. If this was a list of values, I doubt you'd want both objects to be changed.Luganda
Not sure why this is nagging at my brain, but I feel like the full detail of that wasn't clear. Say this was a list of students in a class, and the value in each row is their grade, or their height, or whatever. Its very likely that more than one student would have the same value, so editing them by value would be completely wrong. You would need to know that all values are different, and that they would by necessity ALWAYS be different. Since the request here was to CHANGE values in an EditText, you have multiple problems.Luganda
Problem #1. Since you're searching by value, if you changed the value in the text box, it would only update the list the first time. Say the first value was 5.75. Your handler has a reference to 5.75 in it. Change that value to 6.2. That should work. Now change it again. That will fail. The adapter will still be looking to update 5.75.Luganda
Problem #2 (aka THE BIG PROBLEM) is multiple values, as mentioned. Say this is simply a list of reps done in the gym lifting weights. Its possible that you NEVER do the same number of reps per set, but there's no guarantee. So, say you do a bunch of sets and its: 10, 8, 6, 6. You really kind of cheated, though, so you go back and change the last one to 5. Whoops. Now it says 10, 8, 5, 5. Go back and change the second to last one to 6 again, and its 10, 8, 6, 6. Your users would report "weird data problems", and you'd write it off to stupid users.Luganda
Now that I think about it, it doesn't really matter, right? There's no "update" method on the adapter. You can only remove or insert. Since the original question was how to update values in place, doing it by value reference doesn't really help anyway. You'd need to find position, remove, then insert in that position, which is kind of a mess. Anyway, look, why are you calling "int i = getPosition(d);" anyway? If you want the position, you can just reference 'position' directly. Set it to final.Luganda
S
1

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.

Sholom answered 14/11, 2011 at 0:12 Comment(2)
Why is it looked down upon to have so many EditTexts? I don't think my instance would lend itself well to having just one pop up. Is it the look only, or is it taxing on the phone's memory as well?Marathon
The main reason is during scrolling, ListViews recycle each item once it is hidden from the screen, and uses that recycled view to display the next item from the list. If the recycled view contains data as would your EditText will contain text, that data will most likely be lost or misplaced as you scroll through your ListView.Saurel
S
1

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?

Saurel answered 14/11, 2011 at 0:23 Comment(0)
H
0

I tried to solve this and as you can see there is a simple method - I am posting the answer here as it might be useful for someone.

Not able to get the position when list view -> edit text has a text watcher.

This is the solution that worked for me :

In get view -

when I add the text watcher listener to edit text, I also added the below line

edittext.setTag(R.id.position<any unique string identitiy>, position)

in your afterTextChanged -

 int position = edittext.getTag(R.id.position) 

Gives the correct position number and you can do modifications based on the position number.

Hyoscyamus answered 26/4, 2017 at 12:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.