二级联动列表控件 Linkage-RecyclerView

Apache
Java
Android
2019-05-14
红薯

Linkage-RecyclerView 是一款基于 MVP 架构开发的二级联动列表控件。它是因 “RxJava 魔法师” 这个项目的需求而存在。

在最初寻遍了 GitHub 也没有找到合适的开源库(高度解耦、可远程依赖)之后,我决心研究参考现有开源项目关于二级联动的逻辑,并自己动手编写一个 高度解耦、轻松配置、可通过 maven 仓库远程依赖 的真正的第三方库。

Linkage-RecyclerView 的个性化配置十分简单,依托于 MVP 的 “配置解耦” 特性,使用者无需知道内部的实现细节,仅通过实现 Config 类即可完成功能的定制和扩展。

此外,在不设置自定义配置的情况下,Linkage-RecyclerView 最少只需 一行代码即可运行起来

RxMagic Eleme Linear Eleme Grid
7.gif 2.gif 3.gif

目标

Linkage-RecyclerView 的目标是:一行代码即可接入二级联动列表

除了一键接入而省去 99% 不必要的、复杂的、重复的工作外,你还可以从这个开源项目获得的内容包括:

  1. 整洁的代码风格和标准的资源命名规范。
  2. MVP 架构在第三库中的最佳实践:使用者无需了解内部逻辑,通过实现接口即可轻松完成个性化配置
  3. 优秀的代码分层和封装思想,在不做任何个性化配置的情况下,一行代码即可接入。
  4. 主体工程基于前沿的、遵循关注点分离的 JetPack MVVM 架构。
  5. AndroidX 和 Material Design 2 的全面使用。
  6. ConstraintLayout 约束布局的最佳实践。
  7. 绝不使用 Dagger,绝不使用奇技淫巧、编写艰深晦涩的代码。

如果你正在思考 如何为项目挑选合适的架构 的话,这个项目值得你参考!

简单使用:

1.在 build.gradle 中添加对该库的依赖。

implementation 'com.kunminx.linkage:linkage-recyclerview:1.3.5'

2.依据默认的分组实体类 DefaultGroupedItem 的结构准备一串数据(以下以 JSON 为例)。

// DefaultGroupedItem.ItemInfo 包含三个字段:
String title //(必填)二级选项的标题
String group //(必填)二级选项所在分组的名称,要和对应的一级选项的标题相同
String content //(选填)二级选项的内容
[
  {
    "header": "优惠",
    "isHeader": true
  },
  {
    "isHeader": false,
    "info": {
      "content": "好吃的食物,增肥神器,有求必应",
      "group": "优惠",
      "title": "全家桶"
    }
  },
  {
    "header": "热卖",
    "isHeader": true
  },
  {
    "isHeader": false,
    "info": {
      "content": "爆款热卖,月销超过 999 件",
      "group": "热卖",
      "title": "烤全翅"
    }
  }
]
    

3.在布局中引入 LinkageRecyclerView 。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.kunminx.linkage.LinkageRecyclerView
        android:id="@+id/linkage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

4.在得到数据后,最少只需一行代码即可完成初始化。

List<DefaultGroupedItem> items = gson.fromJson(...);

//一行代码完成初始化
linkage.init(items);

注意:如使用 JSON,请在 ProGuard Rules 中为该实体类配置混淆白名单:

-keep class com.kunminx.linkage.bean.** {*;}

个性化配置:

该库为一级和二级 Adapter 分别准备了 Config 接口(ILevelPrimaryAdapterConfig 和 ILevelSecondaryAdapterConfig),自定义配置时,即是去实现这两个接口,来取代默认的配置

之所以设置成接口的形式,而非 Builder 的形式,是因为二级联动列表内部的联动逻辑需要指明关键的控件。接口相比 Builder 具有强制性,能够让使用者一目了然必须配置的内容,故而采用接口,通过 MVP 架构的方式来编写该库。

关于个性化配置,具体可以参考我在 ElemeGroupedItem 和 SwitchSampleFragment 中编写的案例:

Step1:根据需求扩展实体类

你需要根据需求,在 BaseGroupedItem 的基础上扩展分组实体类,具体的办法是,编写一个实体类,该实体类须继承于 BaseGroupedItem;该实体类的内部类 ItemInfo 也须继承于 BaseGroupedItem.ItemInfo

以 Eleme 分组实体类为例,扩充 contentimgUrlcost 三个字段:

public class ElemeGroupedItem extends BaseGroupedItem<ElemeGroupedItem.ItemInfo> {

    public ElemeGroupedItem(boolean isHeader, String header) {
        super(isHeader, header);
    }

    public ElemeGroupedItem(ItemInfo item) {
        super(item);
    }

    public static class ItemInfo extends BaseGroupedItem.ItemInfo {
        private String content;
        private String imgUrl;
        private String cost;

        public ItemInfo(String title, String group, String content) {
            super(title, group);
            this.content = content;
        }

        public ItemInfo(String title, String group, String content, String imgUrl) {
            this(title, group, content);
            this.imgUrl = imgUrl;
        }

        public ItemInfo(String title, String group, String content, String imgUrl, String cost) {
            this(title, group, content, imgUrl);
            this.cost = cost;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }

        public String getImgUrl() {
            return imgUrl;
        }

        public void setImgUrl(String imgUrl) {
            this.imgUrl = imgUrl;
        }

        public String getCost() {
            return cost;
        }

        public void setCost(String cost) {
            this.cost = cost;
        }
    }
}

注意:如使用 JSON,请在 ProGuard Rules 中为该实体类配置混淆白名单。

Step2:实现接口,完成自定义配置

在装载数据和实现自定义配置时,泛型框中须指明你编写的实体类,注意 List<ElemeLinkageItem>,以及 new ILevelSecondaryAdapterConfig<ElemeLinkageItem.ItemInfo>() 这两处。

private void initLinkageDatas(LinkageRecyclerView linkage) {
        Gson gson = new Gson();
        List<ElemeGroupedItem> items = gson.fromJson(...);

        linkage.init(items, new ILevelPrimaryAdapterConfig() {

            private Context mContext;

            public void setContext(Context context) {
                mContext = context;
            }

            @Override
            public int getLayoutId() {
                return R.layout.default_adapter_linkage_level_primary;
            }

            @Override
            public int getTextViewId() {
                return R.id.tv_group;
            }

            @Override
            public int getRootViewId() {
                return R.id.layout_group;
            }

            @Override
            public void onBindViewHolder(
                LinkageLevelPrimaryAdapter.LevelPrimaryViewHolder holder, 
                String title, int position) {
                
                holder.getView(R.id.layout_group).setOnClickListener(v -> {
					//TODO
                });
            }

            @Override
            public void onItemSelected(boolean selected, TextView itemView) {
                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));
            }

        }, new ILevelSecondaryAdapterConfig<ElemeGroupedItem.ItemInfo>() {

            private Context mContext;
            private boolean mIsGridMode;

            public void setContext(Context context) {
                mContext = context;
            }

            @Override
            public int getGridLayoutId() {
                return R.layout.adapter_eleme_secondary_grid;
            }

            @Override
            public int getLinearLayoutId() {
                return R.layout.adapter_eleme_secondary_linear;
            }

            @Override
            public int getHeaderLayoutId() {
                return R.layout.default_adapter_linkage_level_secondary_header;
            }

            @Override
            public int getTextViewId() {
                return R.id.iv_goods_name;
            }

            @Override
            public int getRootViewId() {
                return R.id.iv_goods_item;
            }

            @Override
            public int getHeaderViewId() {
                return R.id.level_2_header;
            }

            @Override
            public boolean isGridMode() {
                return mIsGridMode;
            }

            @Override
            public void setGridMode(boolean isGridMode) {
                mIsGridMode = isGridMode;
            }

            @Override
            public int getSpanCount() {
                return 2;
            }

            @Override
            public void onBindViewHolder(
                LinkageLevelSecondaryAdapter.LevelSecondaryViewHolder holder, 
                BaseGroupedItem<ElemeGroupedItem.ItemInfo> item, int position) {
                
                ((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
                });
            }
        });
}
的码云指数为
超过 的项目
加载中

评论(0)

暂无评论

暂无资讯

暂无问答

C++ 匿名namespace的作用以及它与static的区别

一、匿名namespace的作用 在C语言中,如果我们在多个tu(translation unit)中使用了同一个名字做为函数名或者全局变量名,则在链接阶段就会发生重定义错误,为了解决这个问题,我们可以在定...

2015/01/30 04:08
3.2K
1
针对MDS中MDCache中对discover的理解

discover发送端通过调用_send_discover()函数来创建MDiscover类消息,之后将该类消息发送给目的MDS进程。discover可以寻找base inode、Dir、Dentry以及Dentry::linkage对应的Inode。 discov...

2016/08/09 17:40
91
0
MDCache中对dentry unlink的理解

MDCache中send_dentry_unlink()函数负责发送dentry unlink到其他MDS进程。发送端首先得到参数dn下的所有replica_map,若参数中straydn不为空则得到straydn下所有的replica_map。之后遍历得到...

2016/08/09 17:43
88
0
win7: iscsi启动

## win7的安装,修改以及迁移 > 第三部分 win7的安装,修改以及迁移 关于win7的安装不需要多说什么了,把你要网启的机器接上硬盘正常安装就好了。安装好驱动,软件和系统补丁。 为了让win7支...

2017/10/24 08:57
13
0
RecyclerView的两种加载更多的写法比较

关于RecyclerView的加载更多(重写RecyclerView.OnScrollListener),网上普遍有两种写法,来分析下两种写法的优劣: 1. 第一种,代码如下,这种方法的原理是当RecyclerView持有的Item数量减去可见的...

2016/06/30 17:35
37
0
phpcms v9中模板标签和联动菜单的使用方法详解

pc:content action="position" posid="9" order="id"num="10" cache="3600"} pc标签{pc:content 参数名="参数值"参数名="参数值"参数名="参数值"} {loop $data $k $v} <li><a href="{$v[url]...

2016/03/22 17:41
86
1
那些酷炫的RecyclerView开源库整理

序号 项目名称 项目描述 预览 1 cymcsg/UltimateRecyclerView RecyclerView支持各种功能的库,star3000+,Ultimate 这个名字就取得屌 2 CymChad/BaseRecyclerViewAdapterHelper 一个强大并且...

2016/10/28 14:19
43
0
RecyclerView的Item的单击事件

RecyclerView 的每个Item的点击事件并没有像ListView一样封装在组件中,需要Item的单击事件时就需要自己去实现,在Adapter中为RecyclerView添加单击事件参考如下: RecyclerView的使用方法请...

2018/04/20 22:35
449
0
MDCache中对dentry link的理解

MDCache中send_dentry_link()函数负责发送dentry link到其他MDS进程。发送端遍历replica_map数组,得到CDentry对应的CDentry::linkage_t类对象,之后创建MDentryLink类消息,若CDentry::link...

2016/08/09 17:42
164
0

没有更多内容

加载失败,请刷新页面

返回顶部
顶部