-->

Android Multiple EditText fields in a ListAdapter

2020-05-07 07:21发布

问题:

I have a screen (see picture) that is populated by a GridView using a custom extension of BaseAdapter.

When the user enters some text into the EditText fields, the text they entered is liable to shifting around or disappearing entirely. I'm assuming this has to do with the recycling of views, but my understanding of listadapters is poor.

The fields behave fine initially thanks to the Manifest entry android:windowSoftInputMode="adjustPan", but they shift around if you scroll chaotically.

All I am looking to do is get some String data from the user. The Strings are stored in a global String array "strings[]". The strings array is updated by MyTextWatcher, which is just an extension of TextWatcher.

The code (attempts) to ensure that the TextWatchers always know the position of their EditText field within the grid. That way, the TextWatchers should always be updating strings[] with the correct index.

I have every reason to believe that the issue derives from my getView method():

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)  {
            final ViewHolder holder;

            if (convertView == null  || convertView.getTag() == null)  {
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.shape, null);

                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.shape_text);
                holder.image = (ImageView) convertView.findViewById(R.id.shape_image);
                holder.editText = (EditText) convertView.findViewById(R.id.shape_edittext);

                holder.editText.addTextChangedListener(new TextWatcher() {                      
                    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2){}
                    public void onTextChanged(CharSequence s, int start, int before, int count) {
                        if (gameType == SHAPES_ABSTRACT && before == 0 && count == 1) {
                            InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                            mgr.hideSoftInputFromWindow(holder.editText.getWindowToken(), 0);                               
                        }    
                    }
                    public void afterTextChanged(Editable s) {
                        strings[holder.ref]= s.toString(); 
                    }
                });

                convertView.setTag(holder);                    
            }
            else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.ref = position;
            holder.editText.setText(strings[position]);                             

            holder.image.setBackgroundResource(images[position]);

            if (gameType == SHAPES_ABSTRACT)
                holder.text.setText("Seq:");
            else
                holder.text.setVisibility(View.GONE);   

            return convertView;
        }
    };

    grid.setAdapter(listAdapter);
}

回答1:

I would again preface this, as in the other answer, by saying I wouldn't implement it this way. You're doing scary stuff. Carrying lots of references around. However, I think this should help:

Map<EditText, MyTextWatcher> watchers = new HashMap<EditText, MyTextWatcher>();

public View getView(int position, View convertView, ViewGroup parent)
{
    View MyView = convertView;
    if (MyView == null)
    {
        LayoutInflater li = getLayoutInflater();
        MyView = li.inflate(R.layout.shape, null);
    }

    EditText textbox = (EditText) MyView.findViewById(R.id.shape_edittext);

    textbox.setText(strings[position]);
    MyTextWatcher myTextWatcher = watchers.get(textbox);

    if(myTextWatcher == null)
    {
        myTextWatcher = new MyTextWatcher(position, textbox);
        watchers.put(textbox, myTextWatcher);
    }

    myTextWatcher.setIndex(position);

    ImageView image = (ImageView) MyView.findViewById(R.id.shape_image);
    image.setBackgroundResource(images[position]);

    TextView text = (TextView) MyView.findViewById(R.id.shape_text);
    text.setText("Seq:");

    return MyView;
}

The problem here is that you created the TextWatcher, added it to an EditText, but then kept a reference to it in a list by position, so the references between EditText and the TextWatcher were broken.

This solution assumes that 'equals' for EditText will do an object instance compare and not a value compare. If that is NOT the case, you'd need to keep a reference to all EditText instances, and do an '==' compare to each and find a match.

I think there are safer ways to do this, but give it a shot.