Edittext values in listview changes to default on

2020-08-04 06:22发布

问题:

I have a listview with edittext widgets inside each row. I use a viewholder inside my custom adapter to keep track of all the views.

But my problem is that when i input a value inside my edittext, pause until my screen times out, when you unlock the phone while still in the same activity,the default edittext value is refilled, overwriting my edits.

I followed this suggestion here (when listview scroll that time edittext set default value) considering that maybe i am not using the viewholder right but still facing the same issue. It's like getView() keeps getting called, completely redrawing my views.

Any ideas/suggestions on a workaround?

package com.shop.shopOfficer;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;

import com.shopOfficer.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by steve on 2/15/16.
 */
public class ProductListAdapter extends BaseAdapter implements Filterable {
    List<ProductModel> mStringFilterList;
    ValueFilter valueFilter;

    private Activity activity;
    private LayoutInflater inflater;
    private List<ProductModel> modelItems;

    private AddRemoveProductInterface myActivityInterface;

    public ProductListAdapter(Activity activity, List<ProductModel> modelItems, AddRemoveProductInterface myActivityInterface) {
        this.activity = activity;
        this.modelItems = modelItems;
        mStringFilterList = modelItems;
        this.myActivityInterface = myActivityInterface;
    }

    @Override
    public int getCount() {
        return modelItems.size();
    }

    @Override
    public Object getItem(int location) {
        return modelItems.get(location);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolderItem viewHolder;

        if (convertView == null) {

            // inflate the layout
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.product_item_row, null);
            // well set up the ViewHolder
            viewHolder = new ViewHolderItem();
            viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv2);
            viewHolder.price = (TextView) convertView.findViewById(R.id.price);
            viewHolder.p = (TextView) convertView.findViewById(R.id.p);
            viewHolder.minus = (Button) convertView.findViewById(R.id.minus);
            viewHolder.add = (Button) convertView.findViewById(R.id.add);
            viewHolder.quantity = (EditText) convertView.findViewById(R.id.num);
            viewHolder.quantity.addTextChangedListener(new MyTextWatcher(convertView, position));

            viewHolder.add.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //get the value of edittext
                    //add one item
                    int added_item = Integer.parseInt(viewHolder.quantity.getText().toString()) + 1;
                    viewHolder.quantity.setText("" + added_item);
                }
            });
            viewHolder.minus.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    int removed_item = Integer.parseInt(viewHolder.quantity.getText().toString()) - 1;
                    if (removed_item >= 0) {

                        viewHolder.quantity.setText("" + removed_item);
                    } else {

                    }
                }
            });
            // store the holder with the view.
            convertView.setTag(viewHolder);

        } else {
            // we've just avoided calling findViewById() on resource everytime
            // just use the viewHolder
            viewHolder = (ViewHolderItem) convertView.getTag();
        }

        // object item based on the position
        final ProductModel m = modelItems.get(position);
        viewHolder.tvTitle.setText(m.getname());
        viewHolder.price.setText("(" + m.getPrice() + ")");
        viewHolder.p.setText(m.getPrice());
        viewHolder.quantity.setTag(m);
        viewHolder.quantity.setText(String.valueOf(m.getTQuantity()));

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(inflater.getContext(), EditProduct.class);
                intent.putExtra("name", m.getname());
                intent.putExtra("price", m.getPrice());
                intent.putExtra("description", m.getproductDesc());
                intent.putExtra("image_url", m.getImage_url());
                inflater.getContext().startActivity(intent);
            }
        });
        return convertView;
    }

    @Override
    public Filter getFilter() {
        if (valueFilter == null) {
            valueFilter = new ValueFilter();
        }
        return valueFilter;
    }

    private class ValueFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            if (constraint != null && constraint.length() > 0) {
                ArrayList<ProductModel> filterList = new ArrayList<>();
                for (int i = 0; i < mStringFilterList.size(); i++)
                    if ((mStringFilterList.get(i).getname().toUpperCase())
                            .contains(constraint.toString().toUpperCase()) ||
                            (mStringFilterList.get(i).getproductDesc().toUpperCase())
                                    .contains(constraint.toString().toUpperCase())) {


                        ProductModel m = new ProductModel(mStringFilterList.get(i)
                                .getname(), mStringFilterList.get(i)
                                .getproductId(), mStringFilterList.get(i)
                                .getImage_url(), mStringFilterList.get(i)
                                .getPrice(), mStringFilterList.get(i)
                                .getproductDesc());

                        filterList.add(m);
                    }
                results.count = filterList.size();
                results.values = filterList;
            } else {
                results.count = mStringFilterList.size();
                results.values = mStringFilterList;

                //show no results were picked
                //(myActivityInterface).onSearchEmpty("No results found");
            }
            return results;

        }

        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {
            //if(results)
            modelItems = (List<ProductModel>) results.values;
            if (modelItems.size() > 0) {

                notifyDataSetChanged();
            } else {
                (myActivityInterface).onSearchEmpty("No results found");
            }
        }

    }

    static class ViewHolderItem {

        TextView tvTitle, price, p;
        Button add, minus;
        EditText quantity;
    }

    private class MyTextWatcher implements TextWatcher {
        View view;
        int position;

        public MyTextWatcher(View convertView, int position) {
            this.view = convertView;
            this.position = position;
        }

        @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) {
            EditText qtyView = (EditText) view.findViewById(R.id.num);
           // ProductModel m = modelItems.get(position);
            String qtyString = s.toString().trim();
            int quantity = qtyString.equals("") ? 0:Integer.valueOf(qtyString);
            ProductModel m = (ProductModel)qtyView.getTag();
            if(m.getTQuantity() != quantity) {
                m.setTQuantity(quantity);
                String price = ((TextView) view.findViewById(R.id.p))
                        .getText().toString();
                String name = ((TextView) view.findViewById(R.id.tv2))
                        .getText().toString();
                int database_position = 1 + position;
                Log.d("my position", "" + position);
                Log.d("my value", s.toString() + price);
                (myActivityInterface).onAdded(s.toString().trim(), price, database_position, name);
            }
        }
    }
}

Activity code:

public class ProductList extends AppCompatActivity implements AddRemoveProductInterface {

    ListView listView, checkout_listview;
    EditText inputSearch;
    ProductListAdapter adapter;
    ProductCheckoutAdapter checkout_adapter;
    ProductHandler productDB;
    String tag_json_obj = "json_obj_req";
    TextView total;
    String unformatted_number;
    String c_phone, c_zip, c_name, total_amount, checkout_id;
    Boolean c_extras;
    fr.castorflex.android.smoothprogressbar.SmoothProgressBar progbar;
    Button button;
    byte[] b;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_product_list);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        total = (TextView) findViewById(R.id.total);
        total.setText("0.00");
        Intent i = getIntent();
        Bundle extras = i.getExtras();
        if (extras == null) {
            //is null
            c_extras = false;
        } else {
            //has extras
            c_extras = true;
            b = extras.getByteArray("picture");
            c_phone = i.getStringExtra("phone");
            c_zip = i.getStringExtra("zip");
            c_name = i.getStringExtra("name");
            total_amount = i.getStringExtra("total_amount");
            checkout_id = i.getStringExtra("checkout_id");
            if (total_amount == null) {
                total.setText("0.00");
            } else {

                total.setText(total_amount);
            }
            // Toast.makeText(getApplicationContext(), total_amount, Toast.LENGTH_LONG).show();

        }

        productDB = new ProductHandler(this);
       /* sbv = (SlideBottomPanel) findViewById(R.id.sbv);*/

        progbar = (fr.castorflex.android.smoothprogressbar.SmoothProgressBar) findViewById(R.id.prog1);
        listView = (ListView) findViewById(R.id.list);
       // listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        checkout_listview = (ListView) findViewById(R.id.checkout_list);
        // insert data into the list before setting the adapter
        // otherwise it will generate NullPointerException  - Obviously
        productListRequest();
        showProductFromTable();
        inputSearch = (EditText) findViewById(R.id.inputSearch);
        inputSearch.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
                // When user changed the Text
               /* ProductList.this.myList.getFilter().filter(cs);*/
                if (cs.length() > 0) {
                    adapter.getFilter().filter(cs);
                } else {
                    TextView search = (TextView) findViewById(R.id.no_results);
                    search.setVisibility(View.INVISIBLE);
                    adapter.getFilter().filter(cs);
                }

            }

            @Override
            public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
                                          int arg3) {
                // TODO Auto-generated method stub

            }

            @Override
            public void afterTextChanged(Editable arg0) {
                // TODO Auto-generated method stub
            }
        });

        final LinearLayout animated_layout = (LinearLayout) findViewById(R.id.list_animated);
        //animating product sold list
        button = (Button) findViewById(R.id.submit);
        if (total.getText().toString().equals("0.00")) {
            button.setEnabled(false);
        } else {
            button.setEnabled(true);
        }
        button.setOnClickListener(new View.OnClickListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onClick(View v) {
                Log.d("JSON PROD:", productDB.composeProductSolddJSONfromSQLite());
                Intent intent = new Intent(getApplicationContext(), CustomerIdentify.class);
                intent.putExtra("total_sld", unformatted_number);
                //send extras if exist to identify activity
                if (c_extras) {
                    intent.putExtra("picture", b);
                    intent.putExtra("c_phone", c_phone);
                    intent.putExtra("c_extras", c_extras);
                    intent.putExtra("c_zip", c_zip);
                    intent.putExtra("c_name", c_name);
                }
                startActivity(intent);
              
            }
        });
    }

    private void showProductFromTable() {
        //progbar.setVisibility(View.INVISIBLE);
        ArrayList<ProductModel> modelArrayList = productDB.loadProduct();
        //adding it to the list view.
        adapter = new ProductListAdapter(this, modelArrayList, this);
        listView.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

    public void productListRequest() {
        progbar.setVisibility(View.VISIBLE);
        String url = "http://shopofficer.com/business/products/api";
        JsonArrayRequest jsonObjReq = new JsonArrayRequest(url,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        Log.d("product list response:", response.toString());
                        //listView.setVisibility(View.VISIBLE);
                        progbar.setVisibility(View.INVISIBLE);

                        // Parsing json
                        for (int i = 0; i < response.length(); i++) {
                            try {
                                JSONObject obj = response.getJSONObject(i);
                                String name = obj.getString("name");
                                String price = obj.getString("price");
                                String description = obj.getString("description");
                                String image = obj.getString("image");
                                String product_id = obj.getString("id");
                                /*Log.d("my data is", id + title + description);*/
                                productDB.addProduct(product_id, name, price, description, image, "0");
                                showProductFromTable();
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }

                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                VolleyLog.d("productlist error",
                        "Error: " + error.getMessage());

              /*  Crouton.makeText(AttendantList.this, "Something went wrong, please retry",
                       Style.ALERT, R.id.anchor).show();*/
                progbar.setVisibility(View.INVISIBLE);
            }
        }) {


            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                HashMap<String, String> headers = new HashMap<String, String>();
                headers.put("session_id", getapikey());
                return headers;
            }
        };
        ShopOfficer.getInstance().getRequestQueue().getCache()
                .invalidate(url, true);
        // Adding request to request queue
        ShopOfficer.getInstance().addToRequestQueue(jsonObjReq, tag_json_obj);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_select_product, menu);
        return true;
    }

    @Override
    public void onResume() {
        super.onResume();
        showProductFromTable();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_add_product) {
            Intent intent = new Intent(getApplicationContext(), AddNewProduct.class);
            startActivity(intent);
            return true;
        }
        if (id == android.R.id.home) {
            finish();
            return true;
        }


        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onAdded(String s, String price, int database_position, String name) {

        if (s.equals("")) {
        } else {
            //set total
            Double currentPrice = Double.valueOf(price);
            Double quantity = Double.valueOf(s);
            Double calculated = quantity * currentPrice;
            //Double priceDiff = Double.valueOf(df.format(extPrice - currPrice));
            productDB.update(database_position, "" + calculated);
            DecimalFormat df = new DecimalFormat("0.00##");
            //add commas thousands
            unformatted_number = String.valueOf(df.format(productDB.getTotalOfAmount()));
            double amount = Double.parseDouble(unformatted_number);
            DecimalFormat formatter = new DecimalFormat("#,###.00");
            total.setText("" + formatter.format(amount));
            if (total.getText().toString().equals(".00")) {
                total.setText("0.00");
            }
            //add product sold
            if (quantity < 1) {
                productDB.deleteSingleProductSold(Integer.valueOf(s));
            } else {
                productDB.addSale(database_position, name, String.valueOf(calculated), s);
            }
            if (total.getText().toString().equals("0.00")) {
                button.setEnabled(false);
            } else {
                button.setEnabled(true);
            }

        }
    }

    @Override
    public void onRemoved(String s) {
        total.setText("");
    }

    @Override
    public void onSearchEmpty(String s) {
        // Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();
        TextView search = (TextView) findViewById(R.id.no_results);
        search.setVisibility(View.VISIBLE);
        search.setText(s);
    }
}

回答1:

The views and the viewholders are only temporarily associated with a row. It means that any edited data needs to be stored elsewhere. Otherwise the information is lost every time the views are rebuild or reused for a different row, like when scrolling or rebuilding the view.

One possible solution is to add it to the model :

  • Add the data (quantity) to the ProductModel. (the_quantity)

  • Add a position attribute to the viewholder (the_position). This is needed because the listeners are anonymous classes. They see (a copy of) the value of the position parameter with the value it had when they were instanciated.
    This is why the compiler sometimes complains that the position parameter is not final, and it usually means that something is wrong : You should only reference it outside callbacks.

  • UI -> model : Store the edited value in model (in both listeners)

    public void onClick(View v) {
        //get the value of edittext
        //add one item
        int added_item = Integer.parseInt(viewHolder.quantity.getText().toString()) + 1;
    
        // Store the value in the model
        // We use the position from the viewholder,
        // **not** the method's parameter (which contain 
        // the value when the listener was created)
        modelItems.get(viewHolder.the_position).the_quantity = added_item;
    
        viewHolder.quantity.setText("" + added_item);
    }
    
  • Model -> UI : Update the UI each time, not only when the views a created

    // object item based on the position
    final ProductModel m = modelItems.get(position);
    viewHolder.tvTitle.setText(m.getname());      
    
    // update the viewholder's position 
    viewHolder.the_position = position;
    viewHolder.quantity.setText("" + m.the_quantity)
    
    return convertView;