本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金
概念图
定义实体类
首先定义三个实体类,分别表示全部记录(一级)、日期、全部记录(二级)
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();
}
}
}
复制代码
效果图
其他代码
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);
复制代码
点个赞吧 :)