Search code examples
androidonclicklistenerandroid-adapterandroid-recyclerview

RecyclerView and AdapterView, fails to show items


I was doing some simple examples, to have them as guides, etc. I have a RecyclerView with its own adapter. The items are data models, with a text and an image. The ViewHolder, besides containing the corresponding views, I added a Boolean to control if the image is visible or not.

When I click, for example in the first item, the image disappears (or appears if it is clicked again).

The problem is that if you click (for example the first element) the image disappears as expected, but when scrolling and the recycler loads the new elements not visible, an element suddenly appears with the hidden image.

Debugging a little I see that when loading the element, depends appears with the boolean to true, when in theory should be false.

I can not understand what is happening, since the elements in the list are different.

P.S: As I said, the code is really easy, so don't expect great things.

MainActivity:

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

        recyclerView = (RecyclerView) findViewById(R.id.recycler);

        List<Person> personList = new ArrayList<>();
        personList.add(new Person ("User1"));
        personList.add(new Person ("User2"));
        personList.add(new Person ("User3"));
        personList.add(new Person ("User4"));
        personList.add(new Person ("User5"));
        personList.add(new Person ("User6"));
        personList.add(new Person ("User7"));
        personList.add(new Person ("User8"));
        personList.add(new Person ("User9"));
        personList.add(new Person ("User10"));
        personList.add(new Person ("User11"));
        personList.add(new Person ("User12"));
        personList.add(new Person ("User13"));
        personList.add(new Person ("User14"));
        personList.add(new Person ("User15"));
        personList.add(new Person ("User16"));
        personList.add(new Person ("User17"));
        personList.add(new Person ("User18"));
        personList.add(new Person ("User19"));
        personList.add(new Person ("User20"));
        personList.add(new Person ("User22"));
        personList.add(new Person ("User23"));


        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        CustomImageAdapter adapter = new CustomImageAdapter(personList);
        recyclerView.setAdapter(adapter);

Adapter:

public class CustomImageAdapter extends RecyclerView.Adapter<CustomImageAdapter.ViewHolder> {

    private List<Person> personList;

    public CustomImageAdapter(List<Person> personList) {
        this.personList = personList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.image_person, parent, false);

        return new ViewHolder(itemView);
    }

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

        Person person = personList.get(position);
        holder.name.setText(person.getName());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!holder.imageHide) {
                    holder.image.setVisibility(View.INVISIBLE);

                } else {
                    holder.image.setVisibility(View.VISIBLE);
                }
                holder.imageHide = !holder.imageHide;
            }
        });
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView name;
        ImageView image;
        boolean imageHide = false;


        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(R.id.name);
            image = (ImageView) itemView.findViewById(R.id.image);
            imageHide = false;
        }
    }
}

Data Model:(Just a sneak peak)

public class Person {
    private String name;
    private String image;


    public Person(String name) {
        this.name = name;
    }
    ....
}

Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="30dp"
        android:layout_height="30dp"
        app:srcCompat="@mipmap/ic_launcher"/>
</LinearLayout>

Couple of images as example:

enter image description here

enter image description here


Solution

  • The ViewHolder Class is intended to be used for quick access to "View" Related items such as textviews or imageViews things of that nature.

    When the content needs to be dynamic based on an object model, then the object model needs to drive the visibility pattern. Not a boolean nested inside the viewholder class.

    Think of it this way. The recycler view recycles (n) number of views.

    --View 1 (shows person 1) boolean visible in viewholder 1

    --View 2 (shows person 2) boolean visible in viewholder 1

    --View 3 (shows person 3) boolean visible in viewholder 1

    click on view 1

    --View 1 -- save boolean false "image not visible"

    Current View after Click

    --View 1 (shows person 1) boolean invisible in viewholder 1

    --View 2 (shows person 2) boolean visible in viewholder 1

    --View 3 (shows person 3) boolean visible in viewholder 1

    Now you scroll recycler

    view 1 is off screen and recycled for next onscreen as Person 4 now retaining boolean value in viewholder pattern

    --View 2 (shows person 2) boolean visible in viewholder 2

    --View 3 (shows person 3) boolean visible in viewholder 3

    --View 1 (shows person 4) boolean invisible in viewholder 1

    So to correct this: Simply modify your code like this:

    public class Person {
    private String name;
    private String image;
    private boolean isVisible;
    
    public boolean getIsVisible(){
         return isVisible;
    }
    public void setIsVisible(boolean value){
        isVisible = value;
    }
    
    
    public Person(String name) {
        this.name = name;
    }
    ....
    

    }

    Then modify your adapter like this:

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
    
        final Person person = personList.get(position);
        holder.name.setText(person.getName());
        holder.image.setVisibility(person.getIsVisibility() ? VISIBLE : INVISIBLE);
        onItemClick(holder.root, person, position);
    }
    
       /*///////////////////////////////////////////////////////////////
    // CLICK LISTENERS
    *////////////////////////////////////////////////////////////////
    private void onItemClick(final LinearLayout root, final Person model, final int position){
        root.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                person.setIsVisible(!person.getIsVisible());
                root.findViewById(YOUR_IMAGE_ID).setVisibility(person.getIsVisible() ? VISIBLE : INVISIBLE);
                 //or if you prefer to not findViewById, you can just update person boolean and call
                //notifyDataSetChanged();
    
            }
    
        });
    
    }
    

    Then add the root id to your XML file and viewholder class if you choose to do the findViewById route (recommended as more efficient then notify)

    Another thing you may consider for your guide is to explain how to handle the click outside of the adapter. I favor an interface usually if I'm not doing databinding. Add this to bottom of your adapter.

     public interface ItemSelectedListener {
        void personList_onItemClick(View view, int position, final Person person);
        void personList_onItemLongClick(View view, int position, final Person person);
    
    }
    

    Then in constructor require the ItemSelectedListener and store in the adapter class. Then you can modify your onClick handler to do

      private void onItemClick(final LinearLayout root, final Person person, final int position){
        root.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mItemSelectedListener != null){
                    mItemSelectedListener.personList_onClick(v, position, person);
    
                }
    
            }
    
        });
    
    }
    

    and of course repeat the above for long click handling.

       private void onItemLongClick(final LinearLayout root, final Person person, final int position){
        root.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(mItemSelectedListener != null){
                    mItemSelectedListener.personList_onItemLongClick(v, position, model);
    
                }
    
                return false;
    
            }
    
        });
    
    }
    

    and of course add the listener for long click into your bindview as well

    @Override
        public void onBindViewHolder(final ViewHolder holder, int position) {
    
            final Person person = personList.get(position);
            holder.name.setText(person.getName());
            holder.image.setVisibility(person.getIsVisibility() ? VISIBLE : INVISIBLE);
            onItemClick(holder.root, person, position);
            onItemLongClick(holder.root, person, position);
        }