Linkage-RecyclerView是一款基于MVP架构开发的二级联动列表控件。它是因 “RxJava魔法师” 这个项目的需求而存在。
在最初寻遍了GitHub也没有找到合适的开源库(高度解耦、可远程依赖)之后,我决心研究参考现有开源项目关于二级联动的逻辑,并自己动手编写一个 高度解耦、轻松配置、可通过maven仓库远程依赖 的真正的第三方库。
Linkage-RecyclerView的个性化配置十分简单,依托于MVP的“配置解耦”特性,使用者无需知道内部的实现细节,仅通过实现Config类即可完成功能的定制和扩展。
此外,在不设置自定义配置的情况下,Linkage-RecyclerView最少只需 一行代码即可运行起来。
RxMagicElemeLinearElemeGrid目标Linkage-RecyclerView的目标是:一行代码即可接入二级联动列表。
除了一键接入而省去99%不必要的、复杂的、重复的工作外,你还可以从这个开源项目获得的内容包括:
整洁的代码风格和标准的资源命名规范。MVP架构在第三库中的最佳实践:使用者无需了解内部逻辑,通过实现接口即可轻松完成个性化配置。优秀的代码分层和封装思想,在不做任何个性化配置的情况下,一行代码即可接入。主体工程基于前沿的、遵循关注点分离的JetPackMVVM架构。AndroidX和MaterialDesign2的全面使用。ConstraintLayout约束布局的最佳实践。绝不使用Dagger,绝不使用奇技淫巧、编写艰深晦涩的代码。如果你正在思考 如何为项目挑选合适的架构 的话,这个项目值得你参考!
简单使用:1.在build.gradle中添加对该库的依赖。
implementation'com.kunminx.linkage:linkage-recyclerview:1.3.5'2.依据默认的分组实体类 DefaultGroupedItem 的结构准备一串数据(以下以JSON为例)。
//DefaultGroupedItem.ItemInfo包含三个字段:Stringtitle//(必填)二级选项的标题Stringgroup//(必填)二级选项所在分组的名称,要和对应的一级选项的标题相同Stringcontent//(选填)二级选项的内容[{"header":"优惠","isHeader":true},{"isHeader":false,"info":{"content":"好吃的食物,增肥神器,有求必应","group":"优惠","title":"全家桶"}},{"header":"热卖","isHeader":true},{"isHeader":false,"info":{"content":"爆款热卖,月销超过999件","group":"热卖","title":"烤全翅"}}]3.在布局中引入LinkageRecyclerView。
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="https://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.kunminx.linkage.LinkageRecyclerViewandroid:id="@+id/linkage"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>4.在得到数据后,最少只需一行代码即可完成初始化。
List<DefaultGroupedItem>items=gson.fromJson(...);//一行代码完成初始化linkage.init(items);注意:如使用JSON,请在ProGuardRules中为该实体类配置混淆白名单:
-keepclasscom.kunminx.linkage.bean.**{*;}个性化配置:该库为一级和二级Adapter分别准备了Config接口(ILevelPrimaryAdapterConfig 和 ILevelSecondaryAdapterConfig),自定义配置时,即是去实现这两个接口,来取代默认的配置。
之所以设置成接口的形式,而非Builder的形式,是因为二级联动列表内部的联动逻辑需要指明关键的控件。接口相比Builder具有强制性,能够让使用者一目了然必须配置的内容,故而采用接口,通过MVP架构的方式来编写该库。
关于个性化配置,具体可以参考我在 ElemeGroupedItem 和 SwitchSampleFragment 中编写的案例:
Step1:根据需求扩展实体类你需要根据需求,在 BaseGroupedItem 的基础上扩展分组实体类,具体的办法是,编写一个实体类,该实体类须继承于 BaseGroupedItem;该实体类的内部类 ItemInfo 也须继承于 BaseGroupedItem.ItemInfo。
以Eleme分组实体类为例,扩充 content、imgUrl、cost 三个字段:
publicclassElemeGroupedItemextendsBaseGroupedItem<ElemeGroupedItem.ItemInfo>{publicElemeGroupedItem(booleanisHeader,Stringheader){super(isHeader,header);}publicElemeGroupedItem(ItemInfoitem){super(item);}publicstaticclassItemInfoextendsBaseGroupedItem.ItemInfo{privateStringcontent;privateStringimgUrl;privateStringcost;publicItemInfo(Stringtitle,Stringgroup,Stringcontent){super(title,group);this.content=content;}publicItemInfo(Stringtitle,Stringgroup,Stringcontent,StringimgUrl){this(title,group,content);this.imgUrl=imgUrl;}publicItemInfo(Stringtitle,Stringgroup,Stringcontent,StringimgUrl,Stringcost){this(title,group,content,imgUrl);this.cost=cost;}publicStringgetContent(){returncontent;}publicvoidsetContent(Stringcontent){this.content=content;}publicStringgetImgUrl(){returnimgUrl;}publicvoidsetImgUrl(StringimgUrl){this.imgUrl=imgUrl;}publicStringgetCost(){returncost;}publicvoidsetCost(Stringcost){this.cost=cost;}}}注意:如使用JSON,请在ProGuardRules中为该实体类配置混淆白名单。
Step2:实现接口,完成自定义配置在装载数据和实现自定义配置时,泛型框中须指明你编写的实体类,注意 List<ElemeLinkageItem>,以及 newILevelSecondaryAdapterConfig<ElemeLinkageItem.ItemInfo>() 这两处。
privatevoidinitLinkageDatas(LinkageRecyclerViewlinkage){Gsongson=newGson();List<ElemeGroupedItem>items=gson.fromJson(...);linkage.init(items,newILevelPrimaryAdapterConfig(){privateContextmContext;publicvoidsetContext(Contextcontext){mContext=context;}@OverridepublicintgetLayoutId(){returnR.layout.default_adapter_linkage_level_primary;}@OverridepublicintgetTextViewId(){returnR.id.tv_group;}@OverridepublicintgetRootViewId(){returnR.id.layout_group;}@OverridepublicvoidonBindViewHolder(LinkageLevelPrimaryAdapter.LevelPrimaryViewHolderholder,Stringtitle,intposition){holder.getView(R.id.layout_group).setOnClickListener(v->{//TODO});}@OverridepublicvoidonItemSelected(booleanselected,TextViewitemView){itemView.setBackgroundColor(mContext.getResources().getColor(selected?com.kunminx.linkage.R.color.colorLightBlue:com.kunminx.linkage.R.color.colorWhite));itemView.setTextColor(ContextCompat.getColor(mContext,selected?com.kunminx.linkage.R.color.colorWhite:com.kunminx.linkage.R.color.colorGray));}},newILevelSecondaryAdapterConfig<ElemeGroupedItem.ItemInfo>(){privateContextmContext;privatebooleanmIsGridMode;publicvoidsetContext(Contextcontext){mContext=context;}@OverridepublicintgetGridLayoutId(){returnR.layout.adapter_eleme_secondary_grid;}@OverridepublicintgetLinearLayoutId(){returnR.layout.adapter_eleme_secondary_linear;}@OverridepublicintgetHeaderLayoutId(){returnR.layout.default_adapter_linkage_level_secondary_header;}@OverridepublicintgetTextViewId(){returnR.id.iv_goods_name;}@OverridepublicintgetRootViewId(){returnR.id.iv_goods_item;}@OverridepublicintgetHeaderViewId(){returnR.id.level_2_header;}@OverridepublicbooleanisGridMode(){returnmIsGridMode;}@OverridepublicvoidsetGridMode(booleanisGridMode){mIsGridMode=isGridMode;}@OverridepublicintgetSpanCount(){return2;}@OverridepublicvoidonBindViewHolder(LinkageLevelSecondaryAdapter.LevelSecondaryViewHolderholder,BaseGroupedItem<ElemeGroupedItem.ItemInfo>item,intposition){((TextView)holder.getView(R.id.iv_goods_name)).setText(item.info.getTitle());Glide.with(mContext).load(item.info.getImgUrl()).into((ImageView)holder.getView(R.id.iv_goods_img));holder.getView(R.id.iv_goods_item).setOnClickListener(v->{//TODO});holder.getView(R.id.iv_goods_add).setOnClickListener(v->{//TODO});}});}
评论