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 🙂