Custom ListView adapter. TextChangedListener calls

2019-03-01 11:09发布

问题:

I have the list of travelers with custom adapter what consist two EditText - edtFirstName and edtLastName. I want when user enters text save changes to List, and when next button click send this List to another activity.

My code:

public class TravellersAdapter extends BaseAdapter {

  private List<Traveler> itemsList;
  private LayoutInflater inflater;
  private Activity context;

  public TravellersAdapter(Activity context, List<Traveler> itemsList) {
    super();
    this.itemsList = itemsList;
    this.context = context;
    inflater = LayoutInflater.from(context);
  }

  public int getCount() { return itemsList.size();  }

  public Object getItem(int i) {  return itemsList.get(i);  }

  public View getView(final int position, View view, ViewGroup viewGroup) {
    if (view == null) {
      view = inflater.inflate(R.layout.traveller_item, null);
    }
    Traveler currentItem = (Traveler) getItem(position);

    EditText firstNameView = (EditText) view.findViewById(R.id.edtFirstName);
    firstNameView.addTextChangedListener(new TextWatcher() {

      public void afterTextChanged(Editable editable) {
        currentItem.setFirstName(editable.toString());
      }

    });

    return view;
  }
}

For exemple List itemsList consist 5 items. When I edit 2-4 element all ok, but when I edit first or last element edited value assigned to all element in List. In dubugger i saw that method afterTextChanged calls 5 times with different values of position. How to fix it?

回答1:

in getView method, the parameter position gives the position of the newly created childView, not the clicked childView's position.

use this to get the correct position:

final int actual_position = myList.getPositionForView((View) v.getParent());

in onClick(View v); of the onClickListener of any View. In you case, you must implement onTextChangedListener for that EditText.

here:

myList is the ListView

v is the View you clicked, in this case the childView of the parent(myList).



回答2:

The issue happens because views are reusable (that is by design in Android API). So eventually you may assign more than 1 text watcher to the same text view. And all of the assigned watchers are fired when text inside of the text view is changed.

A quick fix (and non-optimal if the list is really long, say, of 1000+ items) would be to have a map of Traweller -> TextWatcher.

Then inside of getView() you can do this (pseudo-code):

  • check the map if there is a TextWatcher for this Traweller
  • if map does not have any, then create a new TextWatcher, put in the map and assign to EditText
  • otherwise detach the TextWatcher from the EditText and remove from the map
  • create a new TextWatcher, put in the map and assign to EditText


回答3:

Create one more EditText in the screen that is invisible with name invivisbleEt. And do the following thing in the addTextChangedListener

firstNameView.addTextChangedListener(new TextWatcher() {

    @Override
    public void afterTextChanged(Editable editable) {
        if(!firstNameView.isFocused())
            currentItem.setFirstName(editable.toString());
    }

});

Also add this code in the onCreate method for ListView object.

lv.setOnScrollListener(new AbsListView.OnScrollListener() {

    //public boolean scrolling;

    @Override
    public void onScrollStateChanged(AbsListView absListView, int scrollState) {
        invivisbleEt.requestFocus();
    }

    @Override
    public void onScroll(AbsListView absListView, int i, int i1, int i2) {
    }
});