Android RecyclerView Button Click Effect Multiple Times When Scrolling

我们两清 提交于 2019-12-21 06:07:19

问题


I have a RecyclerView which listing a text and a button. If i click button, Toast text shows position and button color will change to #343434. When i click a button this works good but if i cycle my RecyclerView another button color changing but not clicking, only background color changing.

As you can see below gif : First i am clicking Item 1 and Toast text appear,button background change. Then i cycle down and Item 15's button background is changing. Lastly i scroll up to first position Item 1's button color still in original color,and Item 4's button background color is changed.

So this is my code block :

public class HandleTouchRecyclerViewActivity extends BaseActivity implements ObservableScrollViewCallbacks {
private static final String TAG = HandleTouchRecyclerViewActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handletouchrecyclerview);

    ObservableRecyclerView recyclerView = (ObservableRecyclerView) findViewById(R.id.scroll);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);
    recyclerView.setScrollViewCallbacks(this);
    recyclerView.setAdapter(new CustomAdapter(this, getDummyData()));
}

@Override
public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
    Log.v(TAG, "onScrollChanged: scrollY: " + scrollY + " firstScroll: " + firstScroll + " dragging: " + dragging);
}

@Override
public void onDownMotionEvent() {
    Log.v(TAG, "onDownMotionEvent");
}

@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
    Log.v(TAG, "onUpOrCancelMotionEvent: scrollState: " + scrollState);
}

public static class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
    private Context mContext;
    private LayoutInflater mInflater;
    private ArrayList<String> mItems;

    public CustomAdapter(Context context, ArrayList<String> items) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mItems = items;
    }

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

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(mContext, mInflater.inflate(R.layout.list_item_handletouch, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        viewHolder.textView.setText(mItems.get(position));
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        Context context;

        public ViewHolder(Context context, View view) {
            super(view);
            this.context = context;
            this.textView = (TextView) view.findViewById(android.R.id.text1);
            view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    click(getLayoutPosition() + 1,v);

                    v.setBackgroundColor(Color.parseColor("#343434"))

                }
            });
        }

        private void click(int i,View view) {
            String message = "Button " + i + " is clicked";
            view.setBackgroundColor(Color.parseColor("#343434"));
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();

            Log.v(TAG, "click: " + message);
        }
    }
}

}

So , how can i avoid that ? (Screenshots and codes taken in Android Android Observable-ScrollView)


回答1:


You have to store the states of items somewhere, and restore them when onBindViewHolder() is called.

In RecyclerView, the number of views is not same with the item count; it is usually less than that.

Views for item 1-10 would be first created, and if some of them, say, item 1-4 are scrolled out, views created for item 1-4 will be reused for/bound to item 11-14 and so on. (I just made up the numbers. They can vary depending on situations)

In other words, views are created once, and bound multiple times.

In your case,

  • when you scroll down, the view firstly created and bound to item 1 is recycled and bound to item 15.
  • and when you scrolled up again, the view(item 15, dark button) is scrolled out and bound to item 4
  • item 1 is bound to other view that used to be bound to other items before, which has original button color.

You need to keep track of all item states, and make views reflect them in onBindViewHolder()

EDIT:

These are what is happening while you are scrolling and click a button. (For the sake of simplicity, I'll limit the number of items to 10, and assume that at most 3 items can be drawn on screen.)

The initial state is like this:

  • View #1 - text:"Item 1", original color
  • View #2 - text:"Item 2", original color
  • View #3 - text:"Item 3", original color
  • View #4 - (this is pre-created for future use, not shown yet)

Here, onCreateViewHolder() is called 4 times; it inflates a layout resource into a view and creates a view holder for the view. Also onBindViewHolder() is called 4 times; sets the views' texts

then you click Item 1,

  • View #1 - text:"Item 1", dark color
  • View #2 - text:"Item 2", original color
  • View #3 - text:"Item 3", original color
  • View #4 - (not shown yet, original color)

View #1's button color is now dark.

and scroll down a little bit,

  • View #1 - (scrolled out, not visible, dark color)
  • View #2 - text:"Item 2", original color
  • View #3 - text:"Item 3", original color
  • View #4 - text:"Item 4", original color

Now View #1 has been scrolled out. View #4 became visible and onBindViewHolder() sets the view's text to "Item 4".

And scroll more,

  • View #2 - text:"Item 2", original color
  • View #3 - text:"Item 3", original color
  • View #4 - text:"Item 4", original color
  • View #1 - text:"Item 5", dark color (now this view is recycled)

Look at the last line; No views are newly created and onBindViewHolder() is called to set View #1's text to "Item 5". However, your implementation of onBindViewHolder() doesn't set the button color.

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
    viewHolder.textView.setText(mItems.get(position));
}

And your click() method doesn't keep track of which item is clicked.

private void click(int i,View view) {
    String message = "Button " + i + " is clicked";
    view.setBackgroundColor(Color.parseColor("#343434"))
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();

    Log.v(TAG, "click: " + message);
}

It just changes the view's button color, and doesn't care which item is clicked. So there is no way for onBindViewHolder() to check if this item had been clicked before or not.

Here is one possible solution:

// declare an array to check which item has been clicked
private boolean[] mIsItemClicked = new boolean[mItems.size()];

private void initClickedItems() {
    for (int i = 0; i < mIsItemClicked; ++i) {
        mIsItemClicked = false;
    }
}

private void click(int i,View view) {
    mIsItemClicked[i] = !mIsItemClicked[i]; // toggle
    String message = "Button " + i + " is clicked";
    view.setBackgroundColor(Color.parseColor("#343434"))
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();

    Log.v(TAG, "click: " + message);
}

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
    viewHolder.textView.setText(mItems.get(position));
    int buttonColor;
    if (mIsItemClicked[i])
        buttonColor = Color.parseColor("#343434");
    else
        buttonColor = Color.parseColor("#ffffff");  // whatever the original color is
    viewHolder.itemView.findViewById(R.id.button).setBackgroundColor(buttonColor);
}

Whenever onBindViewHolder() is called, it checks if the item is clicked and sets button colors. click() method also checks which item is clicked.




回答2:


You can see nexus5x's answer. He is saying views 1-10 is creating firstly. This is true but not like that , RecyclerView creating items depending on devices screen.For example : you have 2 devices named A and B.Device A has a bigger screen and 10 items fit on screen this is good. Device B has smaller screen and maybe 7 items fit on screen.

So in device A, firstly 10 items created and you scrolldown a bit new items will be creating.In device B , first 7 items will be creating and then bla bla bla..

So this question is solved for me. If wanna add some switch or states on views you must add some fields that representing this states.

Sorry for my bad English.If you dont understand comment below and i will try to explain a bit more :)



来源:https://stackoverflow.com/questions/34365137/android-recyclerview-button-click-effect-multiple-times-when-scrolling

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!