android - ViewHolder 单个 onClick 影响多个列表项

android - ViewHolder single onClick affects multiple list items

我正在使用带有 ViewHolder 模式的自定义列表适配器将视图扩展到我的列表中,该列表显示图像 (width = match_parent)、左侧的一些文本(图像下方)和右侧的按钮(也在图片下方)。

这是适配器的代码 class -

public class DishItemListAdapter extends ArrayAdapter<DishItem> {

    //declare fonts - BOLD and LIGHT
    final Typeface tf_light = Typeface.createFromAsset(getContext().getAssets(),
            "fonts/Roboto-Thin.ttf");
    final Typeface tf_bold = Typeface.createFromAsset(getContext().getAssets(),
            "fonts/Roboto-Regular.ttf");

    //get item count
    CartItemCount cartItemCount;

    //count for dish at particular position
    ArrayList<Integer> dishCountList = new ArrayList<>();

    //for matching key string in SharedPrefs
    String existingKeyString;

    Typeface font_light, font_bold;
    /* List of DishItem Objects shown on the Dashboard */
    private List<DishItem> DishItemList = new ArrayList<DishItem>();

    public DishItemListAdapter(Context context, int textViewResourceId) {
        super(context, textViewResourceId);
        this.font_bold = tf_bold;
        this.font_light = tf_light;
    }

    /* Add a New DishItem Item (object) to the list of DishItems on the Dashboard i.e. DishItemList */
    @Override
    public void add(DishItem object) {
        DishItemList.add(object);
        super.add(object);
    }

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

    @Override
    public DishItem getItem(int index) {
        return this.DishItemList.get(index);
    }

    @Override
    public View getView(final int position, final View convertView, ViewGroup parent) {
        Log.e("getView() at " + position, "");
        View row = convertView;
        final DishItemViewHolder viewHolder;

        // A ViewHolder keeps references to children views to
        // avoid unnecessary (and expensive) calls to findViewById() on each row.
        if (row == null) {
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.list_item_dish, parent, false);

            //instantiate DishItem View Holder
            viewHolder = new DishItemViewHolder();

            //get BUTTON for Adding Dish to Cart
            viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);

            //BUTTONS for + and - (adding and removing items from cart)
            viewHolder.addItemButton = (Button) row.findViewById(R.id.increase_item_count);
            viewHolder.removeItemButton = (Button) row.findViewById(R.id.decrease_item_count);

            //DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE
            viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);
            viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);
            viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);
            viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);

            //image absolute path
            viewHolder.imageStorePath = new String();

            //image for depicting whether image is VEG or NON VEG
            viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);

            //viewSwitcher for switching between BUTTON and - + button
            viewHolder.viewSwitcher = (ViewSwitcher) row.findViewById(R.id.viewswitcher);

            //indicator for item added to Cart
            viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);
            viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);

            //counter for number of items selected for a particular dish
            viewHolder.dishQuantity = (TextView) row.findViewById(R.id.dish_quantity);

            //set tag for the ViewHolder
            row.setTag(viewHolder);

        } else {
            /* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */
            viewHolder = (DishItemViewHolder) row.getTag();
        }

        //create object of Item Count Class
        cartItemCount = new CartItemCount();


        /* fetch DishItem View at current position (="position") */
        final DishItem dishItem = getItem(position);

        row.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("clicked"+position,"");
                viewHolder.dishName.setText("CLICKED");
            }
        });
        return row;
    }

    static class DishItemViewHolder {
        TextView dishName;
        TextView chefName;
        TextView dishPrice;
        TextView dishQuantity;

        String imageStorePath;

        boolean isDishItemSelected;

        Button addToCart, addItemButton, removeItemButton;

        ImageView veg_nonveg_indicator;

        ImageView dishImage;

        ViewSwitcher viewSwitcher;

        TextView addedToCartIndicator;

    }
}

问题

假设我将 6 个 DishItem bean(模型)添加到列表中。然后,当我对列表中的第一个项目执行 onClick 时,第一个项目的文本应按原样更改为 CLICKED。此外,在日志中,表示 clicked:0(因为第一个列表项的索引为 0)。

但是第 4 个列表项的文本也更改为 CLICKED,这是不应该的。

现在看了thispost讲解ListView的回收机制

但是,我不希望它以这种方式工作,因为我只想更新我单击的那些项目。 我在这里做错了什么? 这种回收机制是否有任何变通方法来仅更新我单击的特定项目?

更新

问题已解决。我遵循了 BlackBelt 的方法,因此接受了他的回答,但我要感谢你们所有人的投入!! :)

这是更新后的 getView() 方法。

 /**

*

* @param position The position of the item within the adapter's data set of the item whose view we want.

* @param convertView The old view to reuse, if possible.

* Note: You should check that this view is non-null and of an appropriate type before using.

* If it is not possible to convert this view to display the correct data, this method

* can create a new view. Heterogeneous lists can specify their number of view types,

* so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).

* @param parent The parent that this view will eventually be attached to

* @return A View corresponding to the data at the specified position.

*/

    @Override

    public View getView(final int position, final View convertView, ViewGroup parent) {

        View row = convertView;

        final DishItemViewHolder viewHolder;


        // A ViewHolder keeps references to children views to

        // avoid unnecessary (and expensive) calls to findViewById() on each row.

        if (row == null) {

            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            row = inflater.inflate(R.layout.list_item_dish, parent, false);


            //instantiate DishItem View Holder

            viewHolder = new DishItemViewHolder();


            //get BUTTON for Adding Dish to Cart

            //viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);

            viewHolder.addItemButton = (Button) row.findViewById(R.id.add_to_cart_secondary_button);

            viewHolder.removeItemButton = (Button) row.findViewById(R.id.remove_from_cart_button);


            //DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE

            viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);

            viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);

            viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);

            viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);


            //image absolute path

            viewHolder.imageStorePath = new String();


            //image for depicting whether image is VEG or NON VEG

            viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);


            //indicator for item added to Cart

            viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);

            viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);


            //set tag for the ViewHolder

            row.setTag(viewHolder);


        } else {

            /* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */

            viewHolder = (DishItemViewHolder) row.getTag();

        }


        //get object for CART ITEM COUNT class

        final CartItemCount cartItemCount = new CartItemCount();


        //get current dish item (MODEL from Bean class)

        final DishItem dishItem = getItem(position);


        //disable any highlighting unless dish is selected (verified from SharedPreferences)

        viewHolder.dishImage.setColorFilter(null);


        //hide ITEM COUNT indicator over the image unless dish is selected (again, verified from SharedPreferences)

        viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);


        //show the + and - buttons on the right and left (respectively) side on the Dish ImageView

        viewHolder.addItemButton.setVisibility(View.VISIBLE);

        viewHolder.removeItemButton.setVisibility(View.VISIBLE);


        //get data from Preferences (to see which dish is selected)

        SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

        Map<String, ?> allEntries = pref.getAll();

        for (Map.Entry<String, ?> entry : allEntries.entrySet()) {

            Log.d("KEY = " + entry.getKey(), " VALUE = " + entry.getValue().toString());

        }


        //get Count for each dish in the list and set Quantity in the Model (DishItem.java)

        if (pref != null) {

            int currentDishCount = pref.getInt("dishCount" + position, 0);

            Log.e("Current DishCount", String.valueOf(currentDishCount));

            if (currentDishCount > 0) {

                getItem(position).setisDishItemSelected(true);

                dishItem.setDishQuantity(currentDishCount);

                Log.d("dish item" + position," selected");

            }

        }


        //update Views for selected DishItems

        if (dishItem.isDishItemSelected()) {

            viewHolder.dishImage.setColorFilter(Color.parseColor("#80E0E0E0"));

            viewHolder.addedToCartIndicator.setVisibility(View.VISIBLE);

            viewHolder.addedToCartIndicator.setText(dishItem.getDishQuantity() + " items in cart");

        }


        viewHolder.addItemButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                dishItem.setisDishItemSelected(true);

                dishItem.setDishQuantity(dishItem.getDishQuantity() + 1);


                //save data to preferences

                SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

                SharedPreferences.Editor editor = pref.edit();

                editor.putInt("dishCount" + position, dishItem.getDishQuantity());

                editor.commit();


                //increment Total Number of Items in Cart

                int itemCount = cartItemCount.getitemCount();

                cartItemCount.setitemCount(itemCount + 1);

                Log.d("itemCount =", String.valueOf(itemCount));

                //broadcast the value of itemCount to MainMenuActivity

                Intent intent = new Intent("NEW_CART_ITEM");

                intent.putExtra("value", cartItemCount.getitemCount());

                getContext().sendBroadcast(intent);

                Log.d("broadcast", "sent");


                //notify adapter of change in underlying data (i.e. update View to show Changes in Model)

                notifyDataSetChanged();



            }

        });



        viewHolder.removeItemButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Log.d("Old Dish Qty. ", String.valueOf(dishItem.getDishQuantity()));

                //if dishCount has reached ZERO, set Dish as NOT SELECTED for buying

                if (dishItem.getDishQuantity() == 0) {

                    dishItem.setisDishItemSelected(false);


                } else {

                    dishItem.setisDishItemSelected(true);

                    dishItem.setDishQuantity(dishItem.getDishQuantity() - 1);

                    Log.d("New Dish Qty.", String.valueOf(dishItem.getDishQuantity()));


                    //decrement TOTAL number of items in Cart

                    int itemCount = cartItemCount.getitemCount();

                    cartItemCount.setitemCount(itemCount - 1);

                    Log.d("itemCount =", String.valueOf(itemCount));

                    //broadcast the value of itemCount to MainMenuActivity

                    Intent intent = new Intent("NEW_CART_ITEM");

                    intent.putExtra("value", cartItemCount.getitemCount());

                    getContext().sendBroadcast(intent);

                    Log.d("broadcast", "sent");


                    //recheck -> if dish Count has reached ZERO, set Dish as NOT SELECTED for buying

                    if (dishItem.getDishQuantity() == 0) {

                        dishItem.setisDishItemSelected(false);

                    }

                }


                //save Current Quantity of Dish Selected to SharedPreferences

                SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);

                SharedPreferences.Editor editor = pref.edit();

                editor.putInt("dishCount" + position, dishItem.getDishQuantity());

                editor.commit();


                //notify adapter of change in underlying data (i.e. update View to show Changes in Model)

                notifyDataSetChanged();

            }

        });


        return row;

    }

思路很简单(之前不知道)-

将数据的变化告知适配器,以便相应地调整视图。

Is there any workaround this recycling mechanism to update only the particular item that I click?

这里没有解决方法。您必须更改数据集(在位置 - 您单击的项目),并要求 Adapter 重绘 ListView 的子项。我强烈建议您也使用 OnItemClickListener

您应该在 getView 中将视图的状态重置为默认状态,然后应用该项目的数据。它是这样工作的:

  1. 获取行的视图(convertView 或扩充一个新视图)
  2. 创建并附加视图持有者
  3. 获取当前项目的数据和状态
  4. 应用所有状态参数和数据(所以如果当前项目被点击则写入 "Clicked" 否则清除 TextView 的文本 - 不要假设它已经包含正确的文本)

您需要将项目的状态存储在某处 - 目前您使用 TextView 的文本来执行此操作,但由于回收行为,它会中断。正确的方法是向 DishItem 添加一个字段,或者只创建一个新的 class 来包含每个项目的状态(选中、聚焦等 - 您想要支持的所有状态) .在 onClickListener 中更改状态的值,然后更改视图的内容或仅在适配器上调用 notifyDataSetChanged

要更改某些内容,您必须知道您的项目的位置:

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Code here
            }
        });

这将为您提供您触摸它的正确位置。其他方式,在您的 getView 内部(在适配器中):

DishItemList = dishCountList.get(position);

这样你就会知道你的项目的位置。只触摸你想要的那个:

row.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View arg0) {
                  // here you can DishItemList.get("and get the key you need");
                }
    });