RecyclerView 执行条目点击

4
我有一个包含可展开项的RecyclerView。单击一项会展开它,问题是它也会意外地展开其他卡片。我已经检查了一切,但我找不到为什么会发生这种情况,但我确实发现,点击的项总是以某种方式具有与其他展开的项相同的ID。只有在列表足够大时才会出现错误,因此我认为它与RecyclerView的功能有关。使用notifyDataSetChanged()可以解决问题,但会消除动画效果,而我想要布局进行动画处理... 此问题似乎讨论了我面临的同样问题...但我还不知道如何解决它。
我无法理解为什么会发生这种情况或如何解决它...以下是一些图像和代码,以帮助您更好地了解,并可能查看问题是否在代码中...
这是RecyclerViewenter image description here 扩展的卡片项如下所示: enter image description here 这是我的适配器类:
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();
        }
    }
}

问题出在哪里?我该如何解决?

非常感谢您的帮助。


我会添加它,但你需要什么代码? - Nitzan Daloomy
@KristyWelsh稍微修改了一下问题...这样更好吗? - Nitzan Daloomy
@Vall0n 我稍微更新了一下问题,加入了我在测试问题时意识到的一些内容...请再看一下(: - Nitzan Daloomy
1
嗨@IronMan,toggleExpanded(...)方法也应在onBindViewHolder(...)方法中使用,以便在滚动时恢复其状态。因此,您应该保存Goal对象的状态,其中状态显示卡片是否已展开。我希望这有助于理解这种方法... - Vall0n
1
你可能遇到了视图状态的问题,需要在onBindViewHolder中设置所有状态,因为旧视图已经展开并正在被回收利用。 - Marcos Vasconcelos
显示剩余9条评论
3个回答

5
问题在于RecyclerView在滚动过程中会重复使用ViewHolders。例如,在位置10,它可以使用来自位置2的ViewHolder(假设此项已展开),如果您不为位置10的ViewHolder绑定扩展/折叠状态,则会保留扩展状态。因此,要解决问题,您必须跟踪ViewHolder状态,并在每个onBindViewHolder方法调用时更新ViewHolder。
以下是与RecyclerView选择相关的良好答案,对于扩展/折叠状态,您将几乎具有相同的逻辑。

https://dev59.com/X14d5IYBdhLWcg3wFPM7#28838834


1
在查看了链接中的解决方案并阅读了博客后,我终于有了一些思路。由于我的项目几乎与它没有任何关系,所以将解决方案应用到我的项目上有点困难,它与布局更改完全无关。但最终,我还是通过你的解决方案成功解决了问题。非常感谢你! 除非有更简单的解决方案,否则我会给你发放奖励(: - Nitzan Daloomy

3

我不熟悉你使用的动画工具,但你可以像这样跟踪和更新视图的可见性:

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);
    });
}

2
你可以参考我的代码来解决问题,也许这会有所帮助。
final boolean isExpanded = position == currentPosition;
holder.childLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
Animation slideDown = AnimationUtils.loadAnimation(context, R.anim.slide_down_animation);
holder.childLayout.startAnimation(slideDown);

if (isExpanded)
    currentPosition = position;

holder.parentLayout.setOnClickListener(v -> {
    currentPosition = isExpanded ? -1 : position;
    notifyItemChanged(currentPosition);
    notifyItemChanged(position);
});

希望这能解决你的问题。
编辑: currentPosition 是一个变量,它被赋值为-1,并且它存储了recyclerview中项目的当前位置。 position 是BindViewHolder的变量。 setActivated() 是为视图定义的方法。你可以在这里查看它:herechildLayout 是展开后显示的视图的布局。 parentLayout 是你点击以展开的布局。

我也遇到了提到的问题,这是我如何解决的。所以基本上使用我提到的方法,并利用位置变量和notifyItemChanged()。 - Piyush Maheswari
@PiyushMaheswari 这段代码应该放在哪里?currentPosition是什么(它在哪里定义,包含什么内容)? 另外,setActivated方法是什么?childLayoutparentLayout是什么?能否请您解释一下?谢谢 (: - Nitzan Daloomy
1
SammyY回答了你的问题。 - Marcos Vasconcelos
代码将位于onBindViewHolder()中,我已经编辑了答案,您可以检查。 - Piyush Maheswari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接