为什么在 RecyclerView 中滚动时 EditText 中的输入值会交换其位置?

Why does the input value in EditText swaps its position while scrolling in a RecyclerView?

在 EditText 中输入内容后,如果快速向上或向下滚动,输入值将交换其在 RecyclerView 中另一个 EditText 中的位置。
在滚动之前,数据位于第一个 EditText 中。
上下滚动后,第一个 EditText 的值将其位置更改为第四个,并且交换是随机的。是否有任何解决方法来稳定数据。

Here is a sample screenshot

这是模型 Class:

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

这是适配器 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();
}

}

这是 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);
    }
  }    
}

你正在调用 etQuantity.addTextChangedListener 但你永远不会删除监听器,所以当视图持有者被回收(也就是用来显示另一个项目)时,它会添加另一个文本更改的监听器(因此它将注册两个不同的文本更改侦听器)。

最简单的解决方法可能是在 onCreateViewHolder 中只调用一次 addTextChangedListener 并使用 getAdapterPosition 而不是 i 来获得位置。

我已经解决了问题。

问题是我在 onBindViewHolder 中添加了 addTextChangedListener。所以 EditText 中的值总是变得混乱,因为当焦点移动时另一个 addTextChangedListener 被注册。所以我所做的是,我实现了另一个名为 CustomEtListener 的自定义 Class 并实现了 TextWatcher。所以现在我在 onCreateViewHolder 中为每个 EditText 注册了自定义侦听器。并使用 array 字符串来存储编辑文本的值。数组的大小是 RecyclerView 中的项目数。 updatePosition 方法用于获取焦点项的位置。然后 onTextChanged 我只是将 editText 的值存储在数组中,并在 onBindViewHolder 中更新了那个 EditText 的值。此后每次滚动时,EditText 的值都不会改变。

这是我的适配器 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) {
    }
}
}

我还是有问题。我无法更新总计列的值。在我上下滚动之后,值发生了变化。所以我必须实现一个调用 backListener 以立即获取 Total 列的值。我现在有一个接口 TextCallBackListener,并在适配器 ProductListHoldersClass 上实现了它。因此,每当调用 onTextChanged 时,它都会触发我的 updateText 方法来更改该行总计的值。如果看不懂,问题里有截图。

这是 TextCallBackListener 接口。

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

这是我更改后的适配器 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) {

    }

}
}

我认为你做得很好。但是您解决的问题可以很容易地解决。您只需要实现一个覆盖方法:

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

代替-

return super.getItemViewType(position);

就Return-

return position;

我想你所有的问题都会以一种简单的方式得到解决。这是你的适配器 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, 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;
}    
}

简单修复:

mFieldAdapter.notifyDataSetChanged();替换为:

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

演示:https://youtu.be/4JpzuLggBV0

以上所有代码都不适合我。我只使用 HashMap 就找到了简单的解决方案。

RecyclerView adapter

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

onBindViewHolder 适配器

    @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());
                            }
                        }
                }
            });


}