Why does the input value in EditText swaps its pos

2020-02-26 01:05发布

问题:

After putting an input in the EditText, if a scroll up or down very fast the input values swaps its position in another EditText in a RecyclerView.
Before scrolling the data was in the first EditText.
After scrolling up and down the value of the first EditText changed its postion to the 4th one and the swapping is random. Is there any work around to steady the data.

Here is a sample screenshot

Here is the Model Class:

public class Product {    
    public int pId;    
    public String pName;    
    public double unit_price;    
    public double discount;    
}    

Here is the adapter Class:

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductListHolder> {
Context context;
List<Product> productList;

public ProductAdapter(Context c, List<Product> lp){
    this.context = c;
    this.productList = lp;
}

public class ProductListHolder extends RecyclerView.ViewHolder{
    TextView tvName;
    TextView tvPrice;
    TextView tvDiscount;
    TextView tvTotal;
    EditText etQuantity;
    public ProductListHolder(View itemView) {
        super(itemView);
        tvName = (TextView) itemView.findViewById(R.id.tvName);
        tvPrice = (TextView) itemView.findViewById(R.id.tvPrice);
        tvDiscount = (TextView) itemView.findViewById(R.id.tvDiscount);
        tvTotal = (TextView) itemView.findViewById(R.id.tvTotal);
        etQuantity = (EditText) itemView.findViewById(R.id.etQuantity);
    }
}

@Override
public ProductListHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_row, viewGroup, false);
    ProductListHolder ph = new ProductListHolder(v);
    return  ph;
}

@Override
public void onBindViewHolder(final ProductListHolder productListHolder, final int i) {
    productListHolder.tvName.setText(productList.get(i).pName);
    productListHolder.tvPrice.setText(String.valueOf(productList.get(i).unit_price));
    productListHolder.tvDiscount.setText(String.valueOf(productList.get(i).discount));

    productListHolder.etQuantity.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (!s.toString().equals("")){
                double totalPrice = (productList.get(i).unit_price-productList.get(i).discount)* (Double.valueOf(s.toString()));
                productListHolder.tvTotal.setText(String.valueOf(totalPrice));
            }
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
}

@Override
public int getItemCount() {
    return productList.size();
}

}

Here is the MainActivity:

public class MainActivity extends AppCompatActivity {

List<Product> productList;
RecyclerView recyclerView;
RecyclerView.Adapter mAdapter;

String[] names = {"A", "B", "C","D"};
double[] prices = {1000, 2000, 3000, 100};
double[] discounts = {10, 20, 30, 2};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initRecyclerView(); // Initializing Recycler View
    new MyTask().execute();
}

public void initRecyclerView(){
    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
}

private class MyTask extends AsyncTask<Void, Void, List<Product>> {

    @Override
    protected List<Product> doInBackground(Void... params) {
        int sz = 24;
        productList = new ArrayList<Product>();
        for(int i=0; i<sz; i++){
            Product p = new Product();
            p.pId = i%4;
            p.pName = names[i%4];
            p.unit_price = prices[i%4];
            p.discount = discounts[i%4];
            productList.add(p);
        }

        return productList;
    }

    @Override
    protected void onPostExecute(List<Product> products) {
        super.onPostExecute(products);
        mAdapter = new ProductAdapter(MainActivity.this, productList);
        recyclerView.setAdapter(mAdapter);
    }
  }    
}

回答1:

I think you have done a great job. But the problem you solved, can be solved in a very easy way. You just need to implement a Overridden method:

public int getItemViewType(int position) { return super.getItemViewType(position); }

In place of-

return super.getItemViewType(position);

Just Return-

return position;

I think all of your problem will be solved in a easy way. Here is your Adapter Class which I modified a little bit:

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductListHolder> {

Context context;
List<Product> productList;


public ProductAdapter(Context c, List<Product> lp) {
    this.context = c;
    this.productList = lp;
}

public class ProductListHolder extends RecyclerView.ViewHolder {
    TextView tvName;
    TextView tvPrice;
    TextView tvDiscount;
    TextView tvTotal;
    EditText etQuantity;

    public ProductListHolder(View itemView) {
        super(itemView);
        tvName = (TextView) itemView.findViewById(R.id.tvName);
        tvPrice = (TextView) itemView.findViewById(R.id.tvPrice);
        tvDiscount = (TextView) itemView.findViewById(R.id.tvDiscount);
        tvTotal = (TextView) itemView.findViewById(R.id.tvTotal);
        etQuantity = (EditText) itemView.findViewById(R.id.etQuantity);
    }
}

@Override
public ProductListHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {
    View v = LayoutInflater.from(context).inflate(R.layout.single_row, viewGroup, false);
    ProductListHolder ph = new ProductListHolder(v);
    return ph;
}

@Override
public void onBindViewHolder(final ProductListHolder productListHolder, final int i) {

    productListHolder.tvName.setText(productList.get(i).pName);
    productListHolder.tvPrice.setText(String.valueOf(productList.get(i).unit_price));
    productListHolder.tvDiscount.setText(String.valueOf(productList.get(i).discount));

    productListHolder.etQuantity.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            productListHolder.tvTotal.setText(s.toString());
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    });

}

@Override
public int getItemCount() {
    return productList.size();
}

@Override
public int getItemViewType(int position) {
    return position;
}    
}


回答2:

I've sorted out the problem.

The problem was that i added the addTextChangedListener in onBindViewHolder. So the values in the EditText always got jumbled, cause when the focus shifted another addTextChangedListener was registered. So what i did is that, i implemented another Custom Class called CustomEtListener and implemented the TextWatcher. So now i registered the custom listener in onCreateViewHolder for each EditText. And took an array of string to store the values of the edit text. And the size of the array was the number of items in the RecyclerView. And the method updatePosition was there to get the position of the focused item. Then onTextChanged i just stored the value of the editText in the array, and updated the value of that EditText in onBindViewHolder. After that every time i scroll, the value of the EditText won't change.

Here is my adapter class.

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductListHolder> {
Context context;
List<Product> productList;
String[] etValArr;
double[] totalValue;

public ProductAdapter(Context c, List<Product> lp) {
    this.context = c;
    this.productList = lp;
    // Create a new array which size matches the number of Edit Texts
    etValArr = new String[productList.size()];
    // and an array for holding all the values of each edit text
    totalValue = new double[productList.size()];
}

public class ProductListHolder extends RecyclerView.ViewHolder {
    TextView tvName;
    TextView tvPrice;
    TextView tvDiscount;
    TextView tvTotal;
    EditText etQuantity;

    // Instance of a Custom edit text listener
    public CustomEtListener myCustomEditTextListener;

    public ProductListHolder(View itemView, CustomEtListener myLis) {
        super(itemView);
        tvName = (TextView) itemView.findViewById(R.id.tvName);
        tvPrice = (TextView) itemView.findViewById(R.id.tvPrice);
        tvDiscount = (TextView) itemView.findViewById(R.id.tvDiscount);
        tvTotal = (TextView) itemView.findViewById(R.id.tvTotal);
        etQuantity = (EditText) itemView.findViewById(R.id.etQuantity);
        // assign a new instance of addTextChangedListener for each item
        myCustomEditTextListener = myLis;
        etQuantity.addTextChangedListener(myCustomEditTextListener);
    }
}

@Override
public ProductListHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {
    View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_row, viewGroup, false);
    ProductListHolder ph = new ProductListHolder(v, new CustomEtListener());
    return ph;
}

@Override
public void onBindViewHolder(final ProductListHolder productListHolder, int i) {
    productListHolder.tvName.setText(productList.get(i).pName);
    productListHolder.tvPrice.setText(String.valueOf(productList.get(i).unit_price));
    productListHolder.tvDiscount.setText(String.valueOf(productList.get(i).discount));

    // Update the position when focus changes
    productListHolder.myCustomEditTextListener.updatePosition(i);
    // Setting the values accordingly
    productListHolder.etQuantity.setText(etValArr[i]);
    productListHolder.tvTotal.setText(String.valueOf(totalValue[i]));

}

@Override
public int getItemCount() {
    return productList.size();
}

/**
 * Custom class which implements Text Watcher
 */
private class CustomEtListener implements TextWatcher {
    private int position;

    /**
     * Updates the position according to onBindViewHolder
     *
     * @param position - position of the focused item
     */
    public void updatePosition(int position) {
        this.position = position;
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        // Change the value of array according to the position
        etValArr[position] = charSequence.toString();
        // Doing the calculation
        if (!etValArr[position].equals("")) {
            totalValue[position] = (productList.get(position).unit_price - productList.get(position).discount) * (Double.valueOf(etValArr[position]));

        } else {
            totalValue[position] = 0.00;
        }
    }

    @Override
    public void afterTextChanged(Editable editable) {
    }
}
}

Still i had a problem. I wasn't able to update the Total column's values. After i scroll up and down, then the value changed. So i had to implement a call backListener to get the value of the Total column right away. I have now an interface TextCallBackListener, and implemented it on the Adapters ProductListHoldersClass. So whenever the onTextChanged was called it triggered my updateText method to change the value of that rows Total. if you don't understand the view, there is a screenshot in the question.

Here is the interface TextCallBackListener.

public interface TextCallBackListener {
      public void updateText(String val);
}

Here is my changed adapter class.

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductListHolder> {
Context context;
List<Product> productList;
String[] etValArr;
double[] totalValue;

public ProductAdapter(Context c, List<Product> lp) {
    this.context = c;
    this.productList = lp;
    // Create a new array which size matches the number of Edit Texts
    etValArr = new String[productList.size()];
    // and an array for holding all the values of each edit text
    totalValue = new double[productList.size()];
}


public class ProductListHolder extends RecyclerView.ViewHolder implements TextCallBackListener {
    TextView tvName;
    TextView tvPrice;
    TextView tvDiscount;
    TextView tvTotal;
    EditText etQuantity;

    // Instance of a Custom edit text listener
    public CustomEtListener myCustomEditTextListener;

    public ProductListHolder(View itemView, CustomEtListener myLis) {
        super(itemView);
        tvName = (TextView) itemView.findViewById(R.id.tvName);
        tvPrice = (TextView) itemView.findViewById(R.id.tvPrice);
        tvDiscount = (TextView) itemView.findViewById(R.id.tvDiscount);
        tvTotal = (TextView) itemView.findViewById(R.id.tvTotal);
        etQuantity = (EditText) itemView.findViewById(R.id.etQuantity);
        // assign a new instance of addTextChangedListener for each item
        myCustomEditTextListener = myLis;
        etQuantity.addTextChangedListener(myCustomEditTextListener);
    }

    @Override
    public void updateText(String val) {
        tvTotal.setText(val);
    }
}


@Override
public ProductListHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {
    View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_row, viewGroup, false);
    ProductListHolder ph = new ProductListHolder(v, new CustomEtListener());
    return ph;
}

@Override
public void onBindViewHolder(final ProductListHolder productListHolder, int i) {
    productListHolder.tvName.setText(productList.get(i).pName);
    productListHolder.tvPrice.setText(String.valueOf(productList.get(i).unit_price));
    productListHolder.tvDiscount.setText(String.valueOf(productList.get(i).discount));

    // Update the position when focus changes
    productListHolder.myCustomEditTextListener.updatePosition(i, productListHolder);
    // Setting the values accordingly
    productListHolder.etQuantity.setText(etValArr[i]);
    //productListHolder.tvTotal.setText(String.valueOf(totalValue[i]));

}


@Override
public int getItemCount() {
    return productList.size();
}

/**
 * Custom class which implements Text Watcher
 */
private class CustomEtListener implements TextWatcher {
    private int position;
    TextCallBackListener textCallBackListener;

    /**
     * Updates the position according to onBindViewHolder
     *
     * @param position - position of the focused item
     * @param pa       - object of ProductListHolder Class
     */
    public void updatePosition(int position, ProductListHolder pa) {
        this.position = position;
        // assigning it to the instance of the CallBackListener
        textCallBackListener = (TextCallBackListener) pa;
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        // Change the value of array according to the position
        etValArr[position] = charSequence.toString();
        // Doing the calculation
        if (!etValArr[position].equals("")) {
            totalValue[position] = (productList.get(position).unit_price - productList.get(position).discount) * (Double.valueOf(etValArr[position]));
            // triggering the callback function to update the total
            textCallBackListener.updateText(String.valueOf(totalValue[position]));
        } else {
            totalValue[position] = 0.00;
            textCallBackListener.updateText("0.00");
        }
    }

    @Override
    public void afterTextChanged(Editable editable) {

    }

}
}


回答3:

You're calling etQuantity.addTextChangedListener but you're never removing the listener, so when the view holder is recycled (aka used to display another item), it'll have another text changed listener added to it (so it'll have two different text changed listeners registered).

The easiest fix is probably to call addTextChangedListener just once in onCreateViewHolder and use getAdapterPosition instead of i to get the position.



回答4:

Simple fix:

Replace mFieldAdapter.notifyDataSetChanged(); with:

for (int i = 0; i < mFieldAdapter.getItemCount(); i++) {
    // update each item itself
    mFieldAdapter.notifyItemChanged(i, fields.get(i));
}

Demo: https://youtu.be/4JpzuLggBV0



回答5:

All above code not working for me. I found simple solution for it just using HashMap.

Add HashMap on RecyclerView adapter

 private HashMap<String, String> txtValueMap = new HashMap<String, String>();

onBindViewHolder adapter

    @Override
public void onBindViewHolder(final ViewHolder holder, final int position) {

         final PanelVO item1 = (PanelVO) mDataArrayList.get(position);

         //set text on Exittext
         holder.et_panel.setText( item1.getPanelName() );

         //add key and value pair data on HashMap data list
         //using this line when you scroll listview not interchange Edittext data
         //if comment this line so input value in EditText swaps its position while scrolling in a RecyclerView

         txtValueMap.put(item1.getPanelId(),holder.et_panel.getText().toString());

         //add key and value pair data on txtValueMap 

        holder.et_panel.addTextChangedListener(new TextWatcher() {
                @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) {

                        txtValueMap.put(item1.getPanelId(),holder.et_panel.getText().toString());
                        final PanelVO item1 = (PanelVO) mDataArrayList.get(position);

                        Iterator myVeryOwnIterator = txtValueMap.keySet().iterator();

                        while(myVeryOwnIterator.hasNext()) {

                            String key=(String)myVeryOwnIterator.next();
                            String value=(String) txtValueMap.get(key);

                            if(key.equalsIgnoreCase(item1.getPanelId())){
                                //update data in your listview
                                item1.setPanelName(holder.et_panel.getText().toString());
                            }
                        }
                }
            });


}