This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

Concept map

Defining entity Classes

First, define three entity classes representing all records (level 1), date, and all records (level 2)

BillRecordsAllData
Copy the code
BillRecordsDateTitle
Copy the code
BillRecord
Copy the code

For example: the first layer of RecyclerView display date and all records of the second layer of RecyclerView display time and amount data

FirstAdapter

Define two tags in the FirstAdapter to mark the date title and record

/** ITEM type: date title */
private static final int ITEM_TYPE_DATE_TITLE = 1;
/** ITEM type: all records */
private static final int ITEM_TYPE_RECORD = 2;
Copy the code

getItemViewType()

Then check inside getItemViewType()

@Override
public int getItemViewType(int position) {
    Object item = mDataList.get(position);
    return item instanceof BillRecord ? ITEM_TYPE_RECORD : ITEM_TYPE_DATE_TITLE;
}
Copy the code

onCreateViewHolder

I’m going to use dataBinding here, and I’m going to encourage you not to write findViewById(), and I’m going to call the onCreateViewHolder abstract function based on the entity type that getItemViewType() returns

@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)); }}Copy the code

onBindViewHolder

OnBindViewHolder is used to bind the layout and set the Adapter for the secondary RecyclerView

((FirstAHolder)holder).mRecyclerView.setLayoutManager(layoutManager);
inAdapter = newBillAllRecordsSecondAdapter(((List<BillRecord>)((BillRecordsAllData)item).getChildList()),((FragmentActivity)mActivity)) ;//inAdapter.setOnItemClickListener(OutAdapter.this); // Listen for the internal adapter here
 ((FirstAHolder)holder).mRecyclerView.setAdapter(inAdapter);
Copy the code

The complete code

@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 = newBillAllRecordsSecondAdapter(((List<BillRecord>)((BillRecordsAllData)item).getChildList()),((FragmentActivity)mActivity)) ;//inAdapter.setOnItemClickListener(OutAdapter.this); // Remember to listen for the internal adapter here((FirstAHolder)holder).mRecyclerView.setAdapter(inAdapter); }}Copy the code

RecyclerView.ViewHolder

Create two Viewholders to bind entries to the layout that records the date and amount, respectively

/** Date title 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(); }}Copy the code
/** Record ViewHolder */
public class FirstAHolder extends RecyclerView.ViewHolder {
 
    private RecyclerviewItemBillAllRecordsFirstLayoutBinding itemAdapterBinding;
    private RecyclerView mRecyclerView;
    FirstAHolder(RecyclerviewItemBillAllRecordsFirstLayoutBinding binding) {
        super(binding.getRoot());
        this.itemAdapterBinding = binding;
        / / secondary recyclerview
        mRecyclerView = itemAdapterBinding.recyclerview;
    }

    void bind(BillRecordsDateTitle record) { itemAdapterBinding.setActivity(mActivity); itemAdapterBinding.setData(record); itemAdapterBinding.setVm(mViewModel); itemAdapterBinding.executePendingBindings(); }}Copy the code

The first level is done, but the animation is missing, since the elegant implementation must use DiffUtil

DiffUtil

DiffUtil.DiffResult The logic corresponding to the abstract function

areItemsTheSame

Define when old and new elements are the same object (usually a business ID)

areContentsTheSame

Define when the content of the same object is the same (determined by business logic)

getChangePayload

Defines how the contents of the same object differ (the return value is passed in as payloads onBindViewHoder())

public void setDataList(final List<? extends Object> list) {
    // Create the observed
   Observable.create(new ObservableOnSubscribe<DiffUtil.DiffResult>() {
            @Override
            // This method is executed in the main thread by default
            public void subscribe(@NonNull ObservableEmitter<DiffUtil.DiffResult> e) throws Exception {
                DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                        @Override
                        public int getOldListSize(a) {
                            return mDataList.size();
                        }

                        @Override
                        public int getNewListSize(a) {
                            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(); }})// Switch the observed thread to a child thread
        .subscribeOn(Schedulers.newThread())
        // Switching the observer to the main thread needs to be run on Android
        .observeOn(AndroidSchedulers.mainThread())
        // Create an observer and subscribe
        .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(a) {}}); }Copy the code

SecondAdapter

Level 2 Adapter is easier than Level 1, so I don’t need to make type judgments

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(a) {
        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(); }}}Copy the code

rendering

Other code

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(a){
        return this.billRecordsDateTitle;
    }
   
    public void setChildList(List<BillRecord> list){
        this.childList = list;
    }
    public List<BillRecord> getChildList(a){
        return childList;
    }

    
    public String getDate(a){
        return this.Date;
    }
    public String getMonthlyBill(a){
        return this.MonthlyBill; }}Copy the code

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(a) {
        return year;
    }

    public int getMonth(a) {
        return month;
    }

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


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



    public String getDate(a) {
        return this.Date;
    }
    public String getMonthlyBill(a) {
        return this.MonthlyBill; }}Copy the code

BillRecord

Public class BillRecord implements Serializable{** * @author JIULANG * @since 0.6.0 */ ** Expenditure */ public static final int TYPE_EXPENDITURE = RecordEntity.TYPE_EXPENDITURE; /** Public static final int TYPE_INCOME = RecordEntity.TYPE_INCOME; private long id; /** accountId */ private long accountId; /** private long TIME; /** categoryUniqueName */ private String categoryUniqueName; /** categoryName */ private String categoryName; /** categoryIcon */ private String categoryIcon; /** amount */ private double amount; /** Note */ private String notes; /** syncId */ private String syncId; /** syncStatus */ private int syncStatus; /** private int type; }Copy the code

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>
              
            <! -- Category name -->          
            <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"/>

            
            <! - the amount -- -- >
            <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>
Copy the code

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);
Copy the code

Give it a like 🙂