Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView Perform Item Click

I have a RecyclerView that contains expandable items. clicking on an item expands it. The problem is it also expand some other cards, unexpectedly. I checked everything and I couldn't find why is this happening, but I did manage to find out that the clicked item always somehow has the same id as the other expanded item. The error occurs only when the list is big enough, so I think it has something to do with the RecyclerViews functionality. Also using notifyDataSetChanged() works, but it eliminates the animations, and I want the layout to be animated...

this question looks to discuss the same problem I'm facing... but yet I don't know how to solve it.

I couldn't understand why is this happening or how to fix this... below are some images and code to help you understand better, and maybe see if the problem is in the code...

this is the RecyclerView:

enter image description here

An expanded card item looks like this:

enter image description here

Here's my Adapters class:

public class ActiveGoalsAdapter extends RecyclerView.Adapter<ActiveGoalsAdapter.ActiveGoalsViewHolder> {

    private Context context;
    private Cursor cursor;
    private ArrayList<Goal> activeGoals;
    private static boolean[] openedFromParent = new boolean[]{false, true}, editing = new boolean[]{false};

    public ActiveGoalsAdapter(Context context, ArrayList<Goal> activeGoals, Cursor cursor) {
        this.context = context;
        this.activeGoals = activeGoals;
        this.cursor = cursor;
    }

    public class ActiveGoalsViewHolder extends RecyclerView.ViewHolder {

        public LinearLayout shrunkContainer, subGoalsTitleContainer;
        public RelativeLayout expandedContainer, subGoalsRecyclerViewContainer, btnDelete, btnCancel, btnSave;
        public ConstraintLayout editPanel;
        public CustomProgressBar shrunkProgressBar, expandedProgressBar;
        public ImageButton btnExpandShrink, btnEdit, btnBackToParent;
        public TextView title, description;
        public RecyclerView subGoalsRecyclerView;
        public ExtendedEditText nameET, descriptionET;

        public ActiveGoalsViewHolder(@NonNull View itemView) {
            super(itemView);

            shrunkContainer = itemView.findViewById(R.id.shrunk_active_goal_container);
            expandedContainer = itemView.findViewById(R.id.expanded_active_goal_container);
            editPanel = itemView.findViewById(R.id.edit_panel);
            btnExpandShrink = itemView.findViewById(R.id.active_goal_expand_shrink_btn);
            btnEdit = itemView.findViewById(R.id.active_goal_edit_btn);
            btnBackToParent = itemView.findViewById(R.id.active_goal_back_to_parent_btn);
            shrunkProgressBar = itemView.findViewById(R.id.shrunk_active_goal_progress_bar);
            shrunkProgressBar.enableDefaultGradient(true);
            title = itemView.findViewById(R.id.expanded_active_goal_title);
            expandedProgressBar = itemView.findViewById(R.id.expanded_active_goal_progress_bar);
            expandedProgressBar.enableDefaultGradient(true);
            description = itemView.findViewById(R.id.expanded_active_goal_description);
            subGoalsTitleContainer = itemView.findViewById(R.id.expanded_active_goal_sub_goals_title_container);
            subGoalsRecyclerViewContainer = itemView.findViewById(R.id.expanded_active_goal_sub_goals_container);
            subGoalsRecyclerView = itemView.findViewById(R.id.expanded_active_goal_sub_goals_recyclerview);
            nameET = itemView.findViewById(R.id.expanded_active_goal_edit_name_edit_text);
            descriptionET = itemView.findViewById(R.id.expanded_active_goal_edit_description_edit_text);
            btnDelete = itemView.findViewById(R.id.edit_delete_button);
            btnCancel = itemView.findViewById(R.id.edit_cancel_button);
            btnSave = itemView.findViewById(R.id.edit_save_button);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (expandedContainer.getVisibility() == View.VISIBLE) {
                        shrink();
                    } else {
                        expand();
                    }
                }
            });

        }

        private void expand(){
            TransitionManager.beginDelayedTransition((ViewGroup) itemView.getRootView(), new AutoTransition());
            expandedContainer.setVisibility(View.VISIBLE);
            shrunkProgressBar.setVisibility(View.INVISIBLE);

        }

        private void shrink(){
            TransitionManager.beginDelayedTransition((ViewGroup) itemView.getRootView(), new AutoTransition());
            expandedContainer.setVisibility(View.GONE);
            shrunkProgressBar.setVisibility(View.VISIBLE);
        }

    }

    @NonNull
    @Override
    public ActiveGoalsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.active_goal_card, parent, false);
        return new ActiveGoalsViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ActiveGoalsViewHolder holder, int position) {
        if (activeGoals.get(position) == null) {
            return;
        }
        GoalDBHelper db = new GoalDBHelper(context);

        Goal currentGoal = activeGoals.get(position);
        Cursor subGoalsCursor = db.getSubGoalsCursorOf(currentGoal);
        ArrayList<Goal> subGoalsArrayList = db.getSubGoalsArrayListOf(currentGoal);

        String name = currentGoal.getName(),
                description = currentGoal.getDescription(),
                parent = currentGoal.getParentGoal();
        int timeCounted = currentGoal.getTimeCounted(),
                timeEstimated = currentGoal.getTimeEstimated();

        for (Goal subGoal : activeGoals) {
            if (subGoal.getParentGoal().equals(name)) {
                subGoalsArrayList.add(subGoal);
            }
        }

        holder.shrunkProgressBar.setText(name);
        holder.shrunkProgressBar.setProgress((timeCounted * 100 / timeEstimated));
        holder.shrunkProgressBar.setRadius(300.0f);
        holder.expandedProgressBar.setText("");
        holder.expandedProgressBar.setProgress((timeCounted * 100 / timeEstimated));
        holder.expandedProgressBar.setRadius(300.0f);
        holder.title.setText(name);
        holder.description.setText(description);

        if (subGoalsArrayList.size() <= 0) {
            holder.subGoalsTitleContainer.setVisibility(View.GONE);
            holder.subGoalsRecyclerViewContainer.setVisibility(View.GONE);
        } else {
            holder.subGoalsTitleContainer.setVisibility(View.VISIBLE);
            holder.subGoalsRecyclerViewContainer.setVisibility(View.VISIBLE);
            initSubGoalsAdapter(holder.subGoalsRecyclerView, subGoalsArrayList, subGoalsCursor);
        }

        if (openedFromParent[0]) {
            holder.btnBackToParent.setVisibility(View.VISIBLE);
        } else {
            holder.btnBackToParent.setVisibility(View.GONE);
        }

    }

    public void initSubGoalsAdapter(RecyclerView subGoalsRecyclerView, ArrayList<Goal> subGoals, Cursor subGoalsCursor) {
        GoalsAdapter adapter = new GoalsAdapter(context, subGoals, subGoalsCursor);
        final CarouselLayoutManager layoutManager = new CarouselLayoutManager(CarouselLayoutManager.VERTICAL, false);
        layoutManager.setPostLayoutListener((CarouselLayoutManager.PostLayoutListener) new CarouselZoomPostLayoutListener());
        subGoalsRecyclerView.setLayoutManager(layoutManager);
        subGoalsRecyclerView.setHasFixedSize(true);
        subGoalsRecyclerView.setAdapter(adapter);
    }

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

    public void swapCursor(Cursor newCursor) {
        if (cursor != null) {
            cursor.close();
        }

        cursor = newCursor;

        if (newCursor != null) {
            notifyDataSetChanged();
        }
    }
}

Where is the problem? and how should I fix it?

Help would be highly appreciated

like image 343
Nitzan Daloomy Avatar asked Jan 01 '23 08:01

Nitzan Daloomy


2 Answers

The problem is that RecyclerView reuses ViewHolders during scrolling. For example on position 10 it can uses ViewHolder from position 2 (let's imagine this item was expanded) and if you don't bind expanded / collapsed state for ViewHolder on position 10 it will have expanded state. So to solve the problem you have to track ViewHolder state and update ViewHolder every onBindViewHolder method calling.

Here is a good answer related to selection in RecyclerView and you will have almost the same logic for expanded / collapsed states.

https://stackoverflow.com/a/28838834/9169701

like image 196
Alex Avatar answered Jan 13 '23 12:01

Alex


I'm not familiar with the utilities you're using for animation. But, you can do something like this to track and update the visibility of your views:

private ArrayList<MyData> dataList;
private ArrayList<boolean> itemStates; // Create a list to store the item states

public MyAdapter(ArrayList<MyData> myData){
    dataList = myData;
    itemStates = new ArrayList<>();

    // Build the default state values for each position
    for(MyData data: dataList){
        itemStates.add(false);
    }
}

@Override
public void onBindViewHolder(MyHolder holder, int position){
    // Whatever you need to do on each item position ...

    final boolean visible = itemStates.get(position);

    // Set the visibility of whichever view you want
    if(visible){
        holder.myView.setVisibility(View.VISIBLE);
    }else{
        holder.myView.setVisibility(View.GONE);
    }

    // Change the visibility after clicked
    holder.itemView.setOnClickListener(new View.OnClickListener(){
        // Use the ViewHolder's getAdapterPosition()
        // to retrieve a reliable position inside the click callback
        int pos = holder.getAdapterPosition();

        if(visible){
            // Play the hide view animation for this position ...
        }else{
            // Play the show view animation for this position ...
        }

        // Set the new item state
        itemStates.set(pos, !visible);

        // Refresh the Adapter after a delay to give your animation time to play
        // (I've used 500 milliseconds here)
        new Handler().postDelayed(new Runnable(){
            @Override
            public void run(){
                notifyDataSetChanged();
            }
        }, 500);
    });
}
like image 33
Sammy T Avatar answered Jan 13 '23 14:01

Sammy T