RecyclerView 执行项目点击
RecyclerView Perform Item Click
我有一个 RecyclerView
,其中包含可扩展的项目。单击一个项目将其展开。问题是它还扩展了一些其他卡,出乎意料。我检查了所有内容,但找不到发生这种情况的原因,但我确实设法发现被点击的项目总是以某种方式与其他展开的项目具有相同的 ID。该错误仅在列表足够大时发生,因此我认为它与 RecyclerView
s 功能有关。使用 notifyDataSetChanged()
也可以,但它消除了动画,我希望布局具有动画效果...
this 问题看起来是在讨论我面临的相同问题...但我不知道如何解决它。
我不明白为什么会发生这种情况或如何解决这个问题...下面是一些图片和代码可以帮助您更好地理解,也许可以看看问题是否出在代码中...
这是 RecyclerView
:
展开的卡片项目如下所示:
这是我的适配器 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();
}
}
}
问题出在哪里?我该如何解决?
不胜感激
你可以参考我的代码来解决,也许会有帮助。
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()
是为视图定义的方法。你可以看看here.
childLayout
是展开后显示的view的布局
parentLayout
是您点击展开的布局。
我不熟悉您用于动画的实用程序。但是,您可以执行类似这样的操作来跟踪和更新视图的可见性:
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);
});
}
问题在于 RecyclerView 在滚动过程中重用了 ViewHolders。例如,在位置 10 上,它可以使用位置 2 的 ViewHolder(假设此项目已展开),如果您不为位置 10 上的 ViewHolder 绑定展开/折叠状态,它将具有展开状态。因此,要解决此问题,您必须跟踪 ViewHolder 状态并在每次调用 onBindViewHolder 方法时更新 ViewHolder。
这是与 RecyclerView 中的选择相关的一个很好的答案,您将拥有几乎相同的展开/折叠状态逻辑。
我有一个 RecyclerView
,其中包含可扩展的项目。单击一个项目将其展开。问题是它还扩展了一些其他卡,出乎意料。我检查了所有内容,但找不到发生这种情况的原因,但我确实设法发现被点击的项目总是以某种方式与其他展开的项目具有相同的 ID。该错误仅在列表足够大时发生,因此我认为它与 RecyclerView
s 功能有关。使用 notifyDataSetChanged()
也可以,但它消除了动画,我希望布局具有动画效果...
this 问题看起来是在讨论我面临的相同问题...但我不知道如何解决它。
我不明白为什么会发生这种情况或如何解决这个问题...下面是一些图片和代码可以帮助您更好地理解,也许可以看看问题是否出在代码中...
这是 RecyclerView
:
展开的卡片项目如下所示:
这是我的适配器 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();
}
}
}
问题出在哪里?我该如何解决?
不胜感激
你可以参考我的代码来解决,也许会有帮助。
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()
是为视图定义的方法。你可以看看here.
childLayout
是展开后显示的view的布局
parentLayout
是您点击展开的布局。
我不熟悉您用于动画的实用程序。但是,您可以执行类似这样的操作来跟踪和更新视图的可见性:
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);
});
}
问题在于 RecyclerView 在滚动过程中重用了 ViewHolders。例如,在位置 10 上,它可以使用位置 2 的 ViewHolder(假设此项目已展开),如果您不为位置 10 上的 ViewHolder 绑定展开/折叠状态,它将具有展开状态。因此,要解决此问题,您必须跟踪 ViewHolder 状态并在每次调用 onBindViewHolder 方法时更新 ViewHolder。
这是与 RecyclerView 中的选择相关的一个很好的答案,您将拥有几乎相同的展开/折叠状态逻辑。