优雅实现RecyclerView里面嵌套RecyclerView

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金

概念图

微信图片_20211008095418.jpg

定义实体类

首先定义三个实体类,分别表示全部记录(一级)、日期、全部记录(二级)

BillRecordsAllData
复制代码
BillRecordsDateTitle
复制代码
BillRecord
复制代码

例如:
第一层的RecyclerView显示日期和全部记录
第二层的RecyclerView显示时间金额数据

FirstAdapter

在FirstAdapter里面定义两个标签,用来标记日期标题和记录

/** ITEM 类型:日期标题 */
private static final int ITEM_TYPE_DATE_TITLE = 1;
/** ITEM 类型:全部记录 */
private static final int ITEM_TYPE_RECORD = 2;
复制代码

getItemViewType()

接着在getItemViewType()里面判断

@Override
public int getItemViewType(int position) {
    Object item = mDataList.get(position);
    return item instanceof BillRecord ? ITEM_TYPE_RECORD : ITEM_TYPE_DATE_TITLE;
}
复制代码

onCreateViewHolder

这里用dataBinding演示,推荐大家使用,不要再写繁琐的findViewById()
根据getItemViewType()返回的实体类型,在onCreateViewHolder抽象函数里面判断ViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         
    if (viewType == ITEM_TYPE_RECORD) {
        return new FirstAHolder(RecyclerviewItemBillAllRecordsFirstLayoutBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }else{
        return new DateTitleViewHolder(RecyclerviewItemBillAllRecordsTitleLayoutBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }
}
复制代码

onBindViewHolder

onBindViewHolder用来绑定布局并且给二级RecyclerView设置Adapter

((FirstAHolder)holder).mRecyclerView.setLayoutManager(layoutManager);
inAdapter = new BillAllRecordsSecondAdapter(((List<BillRecord>)((BillRecordsAllData)item).getChildList()),((FragmentActivity)mActivity));
    //inAdapter.setOnItemClickListener(OutAdapter.this);//在这里监听内部adapter
 ((FirstAHolder)holder).mRecyclerView.setAdapter(inAdapter);
复制代码

完整代码

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
   
    Object item = mDataList.get(position);       
    
    if (holder instanceof DateTitleViewHolder && item instanceof BillRecordsDateTitle) {
        ((DateTitleViewHolder) holder).bind((BillRecordsDateTitle) item);
    }
    if (holder instanceof FirstAHolder && item instanceof BillRecordsAllData) {
        ((FirstAHolder) holder).bind(((BillRecordsAllData)item).getBillRecordsDateTitle());
        
        
        LinearLayoutManager layoutManager = new LinearLayoutManager(mActivity);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        ((FirstAHolder)holder).mRecyclerView.setLayoutManager(layoutManager);
        inAdapter = new BillAllRecordsSecondAdapter(((List<BillRecord>)((BillRecordsAllData)item).getChildList()),((FragmentActivity)mActivity));
            //inAdapter.setOnItemClickListener(OutAdapter.this);//记得在这里监听内部adapter
         ((FirstAHolder)holder).mRecyclerView.setAdapter(inAdapter);
    }
}
复制代码

RecyclerView.ViewHolder

创建两个ViewHolder,分别给记录日期和金额的布局绑定条目

/** 日期标题 ViewHolder */
private class DateTitleViewHolder extends RecyclerView.ViewHolder {

    private RecyclerviewItemBillAllRecordsTitleLayoutBinding mBinding;

    DateTitleViewHolder(RecyclerviewItemBillAllRecordsTitleLayoutBinding binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    void bind(BillRecordsDateTitle title) {
        mBinding.setActivity(mActivity);
        mBinding.setData(title);
        mBinding.setVm(mViewModel);
        mBinding.executePendingBindings();
    }
}
复制代码
/** 记录 ViewHolder */
public class FirstAHolder extends RecyclerView.ViewHolder {
 
    private RecyclerviewItemBillAllRecordsFirstLayoutBinding itemAdapterBinding;
    private RecyclerView mRecyclerView;
    FirstAHolder(RecyclerviewItemBillAllRecordsFirstLayoutBinding binding) {
        super(binding.getRoot());
        this.itemAdapterBinding = binding;
        //二级recyclerview
        mRecyclerView = itemAdapterBinding.recyclerview;
    }

    void bind(BillRecordsDateTitle record) {
        itemAdapterBinding.setActivity(mActivity);
        itemAdapterBinding.setData(record);
        itemAdapterBinding.setVm(mViewModel);          
        itemAdapterBinding.executePendingBindings();
    }
}
复制代码

一级搞定了,但还漏了动画,既然要优雅实现肯定要用DiffUtil

DiffUtil

DiffUtil.DiffResult抽象函数对应的逻辑

areItemsTheSame

定义什么情况下新老元素是同一个对象(通常是业务id)

areContentsTheSame

定义什么情况下同一对象内容是否相同 (由业务逻辑决定)

getChangePayload

具体定义同一对象内容是如何地不同 (返回值会作为payloads传入onBindViewHoder())

public void setDataList(final List<? extends Object> list) {
    //创建被观察者
   Observable.create(new ObservableOnSubscribe<DiffUtil.DiffResult>() {
            @Override
            //默认在主线程里执行该方法
            public void subscribe(@NonNull ObservableEmitter<DiffUtil.DiffResult> e) throws Exception {
                DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                        @Override
                        public int getOldListSize() {
                            return mDataList.size();
                        }

                        @Override
                        public int getNewListSize() {
                            return list.size();
                        }

                        @Override
                        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {

                            Object oldItem = mDataList.get(oldItemPosition);
                            Object newItem = list.get(newItemPosition);

                            if (oldItem instanceof BillRecordsDateTitle && newItem instanceof BillRecordsDateTitle) {
                                double oldStr = ((BillRecordsDateTitle)oldItem).getSurplus();
                                double newStr = ((BillRecordsDateTitle)newItem).getSurplus();
                                return oldStr == newStr;
                                
                            }
                            if (oldItem instanceof BillRecordsAllData && newItem instanceof BillRecordsAllData) {
                                int oldStr = ((BillRecordsAllData)oldItem).getChildList().size();
                                int newStr = ((BillRecordsAllData)newItem).getChildList().size();
                                return oldStr == newStr;

                            }
                            return false;
                            //  return mDataList.get(oldItemPosition).getClass() == list.get(oldItemPosition).getClass();            
                        }

                        @Override
                        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                            Object oldItem = mDataList.get(oldItemPosition);
                            Object newItem = list.get(newItemPosition);

                            if (oldItem instanceof BillRecordsDateTitle && newItem instanceof BillRecordsDateTitle) {
                                double oldStr = ((BillRecordsDateTitle)oldItem).getSurplus();
                                double newStr = ((BillRecordsDateTitle)newItem).getSurplus();
                                return oldStr == (newStr);
                            }
                            if (oldItem instanceof BillRecordsAllData && newItem instanceof BillRecordsAllData) {
                                long oldStr = ((BillRecordsAllData)oldItem).getBillRecordsDateTitle().getDay();
                                long newStr = ((BillRecordsAllData)newItem).getBillRecordsDateTitle().getDay();
                                return oldStr == newStr;
                            }                  
                            return true;
                        }

                    });
                e.onNext(result);

                e.onComplete();
            }
        })
        //将被观察者切换到子线程
        .subscribeOn(Schedulers.newThread())
        //将观察者切换到主线程  需要在Android环境下运行
        .observeOn(AndroidSchedulers.mainThread())
        //创建观察者并订阅
        .subscribe(new Observer<DiffUtil.DiffResult>() {

            @Override
            public void onSubscribe(Disposable p1) {
            }

            @Override
            public void onNext(DiffUtil.DiffResult result) {              
                mDataList.clear();
                mDataList.addAll(list);
                result.dispatchUpdatesTo(BillAllRecordsFirstAdapter.this);                                      
            }
            @Override
            public void onError(Throwable p1) {
            }

            @Override
            public void onComplete() {
            }

        });
}
复制代码

SecondAdapter

二级的Adapter比一级的简单,不用做类型判断我直接贴出来

public class BillAllRecordsSecondAdapter extends RecyclerView.Adapter<BillAllRecordsSecondAdapter.SecondHolder> {

    
    private List<BillRecord> mDataList = new ArrayList<>();
    private BillRecordItemViewModel mViewModel;
    private Activity mActivity;
    
    public BillAllRecordsSecondAdapter (List<BillRecord> List ,FragmentActivity activity){
        mActivity = activity;
        this.mDataList = List;
        mViewModel = new BillRecordItemViewModel(Base.getAppContext());
    }
    @Override
    public BillAllRecordsSecondAdapter.SecondHolder onCreateViewHolder(ViewGroup parent, int position) {
        return new SecondHolder(RecyclerviewItemBillAllRecordsSecondLayoutBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
 
    }

    @Override
    public void onBindViewHolder(BillAllRecordsSecondAdapter.SecondHolder holder, int position) {
        BillRecord item = mDataList.get(position);
        ((SecondHolder) holder).bind((BillRecord) item);
    }

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

    public class SecondHolder extends RecyclerView.ViewHolder {
        private RecyclerviewItemBillAllRecordsSecondLayoutBinding itemAdapterBinding;

        SecondHolder(RecyclerviewItemBillAllRecordsSecondLayoutBinding binding) {
            super(binding.getRoot());
            this.itemAdapterBinding = binding;
        }

        void bind(BillRecord record) {
            itemAdapterBinding.setActivity(mActivity);
            itemAdapterBinding.setData(record);
            itemAdapterBinding.setVm(mViewModel);
            itemAdapterBinding.executePendingBindings();
        }
    }
}
复制代码

效果图

微信图片_20211008114137.jpg

其他代码

BillRecordsAllData

public class BillRecordsAllData {
    
    private int year;
    private int month;
    private int day;
    private double surplus;
    private String Date,MonthlyBill;
    
    private List<BillRecord> childList;
    private BillRecordsDateTitle billRecordsDateTitle;
    
    public BillRecordsAllData(BillRecordsDateTitle mBillRecordsDateTitle) {
        this.billRecordsDateTitle = mBillRecordsDateTitle;
        
    }
    public BillRecordsDateTitle getBillRecordsDateTitle(){
        return this.billRecordsDateTitle;
    }
   
    public void setChildList(List<BillRecord> list){
        this.childList = list;
    }
    public List<BillRecord> getChildList(){
        return childList;
    }

    
    public String getDate(){
        return this.Date;
    }
    public String getMonthlyBill(){
        return this.MonthlyBill;
    }
}
复制代码

BillRecordsDateTitle

public class BillRecordsDateTitle {

    private int year;
    private int month;
    private int day;
    private double surplus;

    private double expenditureToday;
    private double incomeToday;

    public BillRecordsDateTitle(int month, int day, double surplus) {
        this.month = month;
        this.day = day;
        this.surplus = surplus;
    }

    public int getYear() {
        return year;
    }

    public int getMonth() {
        return month;
    }

    public int getDay() {
        return this.day;
    }
    public double getSurplus() {
        
        return this.surplus;
     }


    private String Date,MonthlyBill;
    public BillRecordsDateTitle(int month, int day) {
        this.month = month;
        this.day = day;
    }



    public String getDate() {
        return this.Date;
    }
    public String getMonthlyBill() {
        return this.MonthlyBill;
    }
} 
复制代码

BillRecord

public class BillRecord implements Serializable{
    
    /**
     * @author JIULANG
     * @since 0.6.0
     */   

        /** 记录类型: 支出 */
        public static final int TYPE_EXPENSE = RecordEntity.TYPE_EXPENDITURE;
        /** 记录类型: 收入 */
        public static final int TYPE_INCOME = RecordEntity.TYPE_INCOME;

        private long id;

        /** 账户 ID */
        
        private long accountId;

        /** 记录时间(UNIX TIME) */
        
        private long time;

        /** 分类唯一不变名称 */
        
        private String categoryUniqueName;

        /** 分类名称 */
        
        private String categoryName;

        /** 分类图标名称 */
        
        private String categoryIcon;

        /** 金额 */
        
        private double amount;

        /** 备注 */
        
        private String notes;

        /** 同步 ID */
        
        private String syncId;

        /** 同步状态 */
        
        private int syncStatus;

        /** 记录类型 */
        
        private int type;
 }
复制代码

BillItemRecentRecordsLayoutBinding

<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
           
    <variable
        name="activity"
        type="android.app.Activity" />
    
    <variable
        name="data"
        type="cn.jiulang.fragment.accountbook.entity.BillRecord" />

    <variable
        name="vm"
            type="cn.jiulang.fragment.accountbook.viewModel.BillRecordItemViewModel" />
    
</data>
  <com.google.android.material.card.MaterialCardView
        xmlns:android="http://schemas.android.com/apk/res/android"    
        app:cardElevation="0dp"
        app:cardCornerRadius="6dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"     
        android:layout_marginLeft="9dp"
        android:layout_marginRight="9dp"
        android:layout_marginTop="3dp"
        android:layout_marginBottom="3dp">


        <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"           
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <FrameLayout
                android:id="@+id/lyCategoryIcon"            
                android:layout_width="36dp"
                android:layout_height="36dp"
                android:layout_margin="12dp"
                android:padding="3dp"
                selected="@{true}"
                android:background="@{data.type == data.TYPE_EXPENSE ? @drawable/bg_category_expense : @drawable/bg_category_income}"              
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"            
                app:layout_constraintBottom_toBottomOf="parent">
                

                <androidx.appcompat.widget.AppCompatImageView
                    android:id="@+id/categoryImageView"               
                    categoryIcon="@{data.categoryIcon}"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                
            </FrameLayout>
              
            <!-- 分类名称 -->          
            <TextView
                android:padding="6dp"
                android:id="@+id/tvCategoryName"         
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="9dp"
                android:text="@{data.categoryName}"           
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintLeft_toRightOf="@id/lyCategoryIcon"
                app:layout_constraintBottom_toBottomOf="parent"/>

            
            <!-- 金额 -->
            <TextView
                android:id="@+id/etAmount"
                textTypeFace="@{Font.QUICKSAND_BOLD}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:gravity="center"
                android:paddingEnd="2dp"
                android:text="@{vm.formatMoney(data)}"
                android:textColor="@color/Black"
                android:textSize="19sp"
                android:textStyle="bold"         
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                
                app:layout_constraintBottom_toBottomOf="parent"/>
            
        </androidx.constraintlayout.widget.ConstraintLayout>

    </com.google.android.material.card.MaterialCardView>
</layout>
复制代码

MainActivity

mRecyclerView = mBinding.recyclerview;
LinearLayoutManager manager = new LinearLayoutManager(getActivity());       
mRecyclerView.setLayoutManager(manager);       
LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.recyclerview_layout_animation_fall_down);
mRecyclerView.setLayoutAnimation(controller);
mAdapter = new BillAllRecordsFirstAdapter(getActivity());
mRecyclerView.setAdapter(mAdapter);
复制代码

点个赞吧 :)

猜你喜欢

转载自juejin.im/post/7016536389344821279