You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView滚动时卡片尺寸随机错乱问题排查与解决

问题描述

需要动态修改recyclerView card尺寸,同步调整卡片内部textViews的间距,共分三种场景:

  • 场景1:金额为空时,RecyclerView卡片内两个TextView紧凑排列,实现代码:
@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        holder1.amount.setText(String.valueOf(currentItem.getAmount()));
        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(constraintLayout_A);
        constraintSet.connect(R.id.amount,ConstraintSet.START, ConstraintSet.PARENT_ID,ConstraintSet.START,70);
        constraintSet.connect(R.id.type, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,70);
        constraintSet.applyTo(constraintLayout_A);
    }
  • 场景2:金额数值过大时,两个TextView切换为上下两行排列,实现代码:
@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        holder2.amount.setText(String.valueOf(currentItem.getAmount()));
        ConstraintSet constraintSet = new ConstraintSet();
        constraintSet.clone(constraintLayout_A);
        constraintSet.connect(R.id.amount,ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,0);
        constraintSet.connect(R.id.amount,ConstraintSet.START, ConstraintSet.PARENT_ID,ConstraintSet.START,0);
        constraintSet.connect(R.id.amount,ConstraintSet.END, ConstraintSet.PARENT_ID,ConstraintSet.END,0);
        constraintSet.connect(R.id.amount,ConstraintSet.BOTTOM, R.id.type,ConstraintSet.TOP,0);
        constraintSet.connect(R.id.type, ConstraintSet.BOTTOM, R.id.info, ConstraintSet.TOP, 0);
        constraintSet.connect(R.id.type, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,0);
        constraintSet.connect(R.id.type, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,0);
        constraintSet.connect(R.id.type, ConstraintSet.TOP, R.id.amount, ConstraintSet.BOTTOM,0);
        constraintSet.connect(R.id.info, ConstraintSet.START,ConstraintSet.PARENT_ID,ConstraintSet.START,0);
        constraintSet.connect(R.id.info, ConstraintSet.END,ConstraintSet.PARENT_ID,ConstraintSet.END,0);
        constraintSet.connect(R.id.info, ConstraintSet.TOP, R.id.type,ConstraintSet.BOTTOM,5);
        constraintSet.applyTo(constraintLayout_A);
    }
  • 场景3:金额为常规值(占2-3个字符位置)时,保持默认布局,实现代码:
@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        holder3.amount.setText(String.valueOf(currentItem.getAmount()));
    }

当前故障

上下滚动recyclerView时,不需要修改布局的列表项会被错误套用布局,比如金额为'0'的项会错误显示为两行布局。已知问题和recylerView的Views复用机制有关。
目前只有设置holder.setIsRecyclable(false);能解决问题,但这个方案性能太差。尝试过拆分三种独立Holder解决,问题依然存在,不确定是实现有误还是思路本身有问题,需要确认正确思路和更优实现方案。

补充适配器代码

package com.example.appName.Adapters;

public class Recycler_Adapter_2 extends ListAdapter<Item_get, RecyclerView.ViewHolder> implements Filterable {

   public Recycler_Adapters_items clickListener;
   private Item_get currentItem;
   private ConstraintLayout constraintLayout_A;
   private Context context;
   private RecHolder holder1BASIC;
   private RecHolder2 holder2EXTENDED;
   private RecHolder4 holder4EXTENDED;
   public Recycler_Adapter_2(String className, RecyclerView recyclerView) {
      super(DIFF_CALLBACK);
      this.className = className;
      ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(recyclerView.getLayoutParams());
      params.setMarginEnd(5);
      context = recyclerView.getContext();
   }
   private static final DiffUtil.ItemCallback<Item_get> DIFF_CALLBACK = new DiffUtil.ItemCallback<Item_get>() {
      @Override
      public boolean areItemsTheSame(@NonNull Item_get oldItem, @NonNull Item_get newItem) {
         return oldItem.getId() == newItem.getId();
      }

      @Override
      public boolean areContentsTheSame(@NonNull Item_get oldItem, @NonNull Item_get newItem) {
         if(oldItem.getBuy_place() != null){
            return oldItem.getname_product().equals(newItem.getname_product()) &&
                    oldItem.getAmount().equals(newItem.getAmount()) && oldItem.getBuy_place().equals(newItem.getBuy_place());
         }else {
            return oldItem.getname_product().equals(newItem.getname_product()) &&
                    oldItem.getAmount().equals(newItem.getAmount());
         }

      }
   };
   public void setOnItemClickListener(Recycler_Adapters_items clickListener){
      this.clickListener = clickListener;
   }

   class RecHolder extends RecyclerView.ViewHolder {
      public TextView name_product;
      public TextView amountBuy;
      public TextView typePackage;

      public RecHolder(@NonNull final View itemView, final Recycler_Adapters_items listener) {
         super(itemView);
         name_product = itemView.findViewById(R.id.name_field);
         amountBuy = itemView.findViewById(R.id.amountBuy);
         typePackage = itemView.findViewById(R.id.typeP);
         constraintLayout_A = itemView.findViewById(R.id.ConstrainLayoutShop1_A);
         initAllNeededArrays();
         itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               if(listener != null){
                  int position = getAbsoluteAdapterPosition();
                  if(position != RecyclerView.NO_POSITION){
                     listener.onItemClick(getItem(position), getAbsoluteAdapterPosition());
                  }
               }
            }
         });
      }
   }

   class RecHolder2 extends RecyclerView.ViewHolder {
      public TextView name_product;
      public TextView amountBuy;
      public TextView typePackage;

      public RecHolder2(@NonNull final View itemView, final Recycler_Adapters_items listener) {
         super(itemView);
         name_product = itemView.findViewById(R.id.name_field);
         amountBuy = itemView.findViewById(R.id.amountBuy);
         typePackage = itemView.findViewById(R.id.typeP);
         constraintLayout_A = itemView.findViewById(R.id.ConstrainLayoutShop1_A);
         initAllNeededArrays();
         itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               if(listener != null){
                  int position = getAbsoluteAdapterPosition();
                  if(position != RecyclerView.NO_POSITION){
                     listener.onItemClick(getItem(position), getAbsoluteAdapterPosition());
                  }
               }
            }
         });
      }
   }
   class RecHolder4 extends RecyclerView.ViewHolder {
      public TextView name_product;
      public TextView amountBuy;
      public TextView typePackage;

      public RecHolder4(@NonNull final View itemView, final Recycler_Adapters_items listener) {
         super(itemView);
         name_product = itemView.findViewById(R.id.name_field);
         amountBuy = itemView.findViewById(R.id.amountBuy);
         typePackage = itemView.findViewById(R.id.typeP);
         constraintLayout_A = itemView.findViewById(R.id.ConstrainLayoutShop1_A);
         initAllNeededArrays();
         itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               if(listener != null){
                  int position = getAbsoluteAdapterPosition();
                  if(position != RecyclerView.NO_POSITION){
                     listener.onItemClick(getItem(position), getAbsoluteAdapterPosition());
                  }
               }
            }
         });
      }
   }
   @NonNull
   @Override
   public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
      View v = null;
      if(className.equals(ShoppList.class.getName())) {
         v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_template2,parent,false);
         switch (viewType){
            case 0:
               return new RecHolder(v,clickListener);
            case 2:
               return new RecHolder2(v,clickListener);
            case 4:
               return new RecHolder4(v,clickListener);
         }
      }
      return new RecHolder(v,clickListener);
   }

   @Override
   public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
      switch (holder.getItemViewType()) {
         case 0:
            holder1BASIC = (RecHolder) holder;
            if (className.equals(ShoppList.class.getName())) {
               currentItem = getItem(position);
               holder1BASIC.name_product.setText(currentItem.getname_product());
               holder1BASIC.amountToBuy.setText(String.valueOf(currentItem.getAmount()));
               setPackageText0(holder1BASIC, currentItem.getType(), currentItem.getAmount());
            }
            break;
         case 2:
            holder2EXTENDED = (RecHolder2) holder;
            if (className.equals(ShoppList.class.getName())) {
               currentItem = getItem(position);
               holder2EXTENDED.name_product.setText(currentItem.getname_product());
               holder2EXTENDED.amountToBuy.setText(String.valueOf(currentItem.getAmount()));
               setPackageText2(holder2EXTENDED, currentItem.getType(), currentItem.getAmount());

            }
            break;
         case 4:
            holder4EXTENDED = (RecHolder4) holder;
            if (className.equals(ShoppList.class.getName())) {
               currentItem = getItem(position);
               holder4EXTENDED.name_product.setText(currentItem.getname_product());
               holder4EXTENDED.amountToBuy.setText(String.valueOf(currentItem.getAmount()));
               setPackageText4(holder4EXTENDED, currentItem.getType(), currentItem.getAmount());
            }
      }
   }


   public void nextLineText(){
      ConstraintSet constraintSet = new ConstraintSet();
      constraintSet.clone(constraintLayout_A);
      constraintSet.connect(R.id.amount,ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,0);
      constraintSet.connect(R.id.amount,ConstraintSet.START, ConstraintSet.PARENT_ID,ConstraintSet.START,0);
      constraintSet.connect(R.id.amount,ConstraintSet.END, ConstraintSet.PARENT_ID,ConstraintSet.END,0);
      constraintSet.connect(R.id.amount,ConstraintSet.BOTTOM, R.id.type,ConstraintSet.TOP,0);
      constraintSet.connect(R.id.type, ConstraintSet.BOTTOM, R.id.info, ConstraintSet.TOP, 0);
      constraintSet.connect(R.id.type, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,0);
      constraintSet.connect(R.id.type, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,0);
      constraintSet.connect(R.id.type, ConstraintSet.TOP, R.id.amount, ConstraintSet.BOTTOM,0);
      constraintSet.connect(R.id.info, ConstraintSet.START,ConstraintSet.PARENT_ID,ConstraintSet.START,0);
      constraintSet.connect(R.id.info, ConstraintSet.END,ConstraintSet.PARENT_ID,ConstraintSet.END,0);
      constraintSet.connect(R.id.info, ConstraintSet.TOP, R.id.type,ConstraintSet.BOTTOM,5);
      constraintSet.applyTo(constraintLayout_A);
   }

   public void setPackageText0( RecHolder holder, int packageTypeCode, int amount){
      if(packageTypeCode == 1){
         if(amount == 0){
            holder.typePackage.setText(packTypeTextResources[2]);
         }else if(amount == 1){
            holder.typePackage.setText(packTypeTextResources[0]);
         }else if(amount >= 2 && amount <= 4){
            holder.typePackage.setText(packTypeTextResources[1]);
         }else if(amount >= 5){
            holder.typePackage.setText(packTypeTextResources[2]);
         }
      }
   }

   public void setPackageText2( RecHolder2 holder2, int packageTypeCode, int amount){
      if(packageTypeCode == 1){
         if(amount == 0){
            holder2.typePackage.setText(packTypeTextResources[2]);
            changeMarginDistance();
         }else if(amount == 1){
            holder2.typePackage.setText(packTypeTextResources[0]);
            changeMarginDistance();
         }else if(amount >= 2 && amount <= 4){
            holder2.typePackage.setText(packTypeTextResources[1]);
            changeMarginDistance();
         }else if(amount >= 5){
            holder2.typePackage.setText(packTypeTextResources[2]);
            changeMarginDistance();
         }
      }else {
         holder2.typePackage.setText(packTypeTextResources[12]);
         changeMarginDistance();
      }
   }

   public void setPackageText4( RecHolder4 holder4, int packageTypeCode, int amount){
      nextLineText();
      if(packageTypeCode == 1){
         if(amount == 0){
            holder4.typePackage.setText(packTypeTextResources[2]);
         }else if(amount == 1){
            holder4.typePackage.setText(packTypeTextResources[0]);
         }else if(amount >= 2 && amount <= 4){
            holder4.typePackage.setText(packTypeTextResources[1]);
         }else if(amount >= 5){
            holder4.typePackage.setText(packTypeTextResources[2]);
         }
      }else {
         holder4.typePackage.setText(packTypeTextResources[12]);
      }
   }

   public Item_get getItemPos(int position){
      return getItem(position);
   }

   private void initPackageArrays(){
      packTypeTextResources = context.getResources().getStringArray(R.array.Package_Type_Diffrent_varieties);
   }

   private void initAllNeededArrays(){
      initPackageArrays();
   }

   @Override
   public int getItemViewType(int position) {
      // return super.getItemViewType(position);
      Item_get actualItem = getItem(position);
      if(actualItem.getAmount() == 0 || actualItem.getAmount() == null){
         return 2;
      }else if (actualItem.getAmount() > 999){
         return 4;
      }
      return 0;
   }

   public void changeMarginDistance(){
      ConstraintSet constraintSet = new ConstraintSet();
      constraintSet.clone(constraintLayout_A);
      constraintSet.connect(R.id.amount,ConstraintSet.START, ConstraintSet.PARENT_ID,ConstraintSet.START,70);
      constraintSet.connect(R.id.type, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,70);
      constraintSet.applyTo(constraintLayout_A);
   }
}

以上为精简后的适配器代码,用于辅助说明问题。


问题原因和修复方案

拆分多Holder的思路本身是对的,但现有代码有两个致命错误,直接导致复用混乱:

  1. 把constraintLayout_A、holder1BASIC、holder2EXTENDED、holder4EXTENDED定义成了适配器的全局成员变量,而不是每个Holder自己的内部属性。onBindViewHolder执行时会反复覆盖这些全局变量,调用nextLineText()、changeMarginDistance()的时候,操作的根本不是当前绑定项的布局,而是上一次绑定的Holder持有的布局,自然会出现布局错乱。
  2. 三个Holder虽然类名不同,但onCreateViewHolder里三个viewType用的是完全一样的布局文件R.layout.recycler_template2,本质上没有做到布局隔离,RecyclerView复用池里不会按期望的类型隔离缓存,还是会出现跨类型复用的问题。

最优修复步骤

第一步:删掉适配器里的全局错误变量

把适配器类里下面这几个全局成员直接删掉:

private ConstraintLayout constraintLayout_A;
private RecHolder holder1BASIC;
private RecHolder2 holder2EXTENDED;
private RecHolder4 holder4EXTENDED;

第二步:把布局操作逻辑收敛到每个Holder内部

每个Holder自己持有自己的constraintLayout_A引用,不要用全局变量。比如修改RecHolder4的逻辑,把nextLineText()变成Holder内部方法,操作自己的布局:

class RecHolder4 extends RecyclerView.ViewHolder {
  public TextView name_product;
  public TextView amountBuy;
  public TextView typePackage;
  private ConstraintLayout constraintLayout_A; // 每个Holder自己持有布局引用

  public RecHolder4(@NonNull final View itemView, final Recycler_Adapters_items listener) {
     super(itemView);
     name_product = itemView.findViewById(R.id.name_field);
     amountBuy = itemView.findViewById(R.id.amountBuy);
     typePackage = itemView.findViewById(R.id.typeP);
     constraintLayout_A = itemView.findViewById(R.id.ConstrainLayoutShop1_A);
     // 删掉initAllNeededArrays,这个逻辑不要放Holder构造里,挪到适配器初始化的时候做
     itemView.setOnClickListener(v -> {
        if(listener != null){
           int position = getAbsoluteAdapterPosition();
           if(position != RecyclerView.NO_POSITION){
              listener.onItemClick(getItem(position), getAbsoluteAdapterPosition());
           }
        }
     });
  }

  // 布局修改方法直接操作当前Holder自己的布局
  public void applyTwoLineLayout(){
     ConstraintSet constraintSet = new ConstraintSet();
     constraintSet.clone(constraintLayout_A);
     constraintSet.connect(R.id.amount,ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,0);
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 23:15:10