Giter Club home page Giter Club logo

multiitem's Introduction

前言

RecyclerView是一个大家常用的列表控件,在列表中不免会出现多种类型的布局,这时adapter中多种类型的判断就会充满着switch的坏味道,可怕的是需求变更,增加或修改新的类型时,所有的改动都在adapter中进行,没有一个良好的扩展性。 MutliItem主要就是解决这些问题,提供了多类型和ViewHolder创建绑定的管理器,自动进行item type的计算,这样Adapter通过依赖倒置与列表中的多类型解耦,还提高了扩展性。有以下特点:

  • 直接使用业务中的实体类为RecyclerView Adapter设置数据源,不需要做任何封装
  • RecyclerView Adapter零编码,解放了复杂的Adapter
  • 支持DataBinding,让你清爽的编写列表代码
  • 支持Form表单录入,懒加载易复用,支持DataBinding、隐藏域、输入内容验证及是否变化
  • 支持Header Footer 和 下拉刷新 加载更多
  • 支持Item滑动动画
  • 支持空白、错误等状态页的展示

系列文章

chat headfoot fullspan loadmore 数据绑定效果 Form表单效果 Form表单提交 详情页效果 空白错误页 动画效果 缩放后跨Recycler拖动

下一步要做什么

  • 思考动画分割线等一些功能封装

用法

添加依赖

  • 配置gradle:

Project rootbuild.gradle中添加:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Module中添加:

dependencies {
    compile 'com.github.free46000:MultiItem:0.9.7'
}
  • 或者你也可以直接克隆源码

多种类型列表用法

这里由于单一和多种类型写法上没有差别,所以就不单独贴出单一类型的列表代码了。 注册多种类型ViewHolderManager,并为adapter设置多种类型数据源:

//初始化adapter
BaseItemAdapter adapter = new BaseItemAdapter();
//为TextBean数据源注册ViewHolderManager管理类
adapter.register(TextBean.class, new TextViewManager());
//为更多数据源注册ViewHolderManager管理类
adapter.register(ImageTextBean.class, new ImageAndTextManager());
adapter.register(ImageBean.class, new ImageViewManager());

//组装数据源list
List<Object> list = new ArrayList<>();
list.add(new TextBean("AAA"));
list.add(new ImageBean(R.drawable.img1));
list.add(new ImageTextBean(R.drawable.img2, "BBB" + i));

//为adapter注册数据源list
adapter.setDataItems(list);

recyclerView.setAdapter(adapter);

ViewHolder管理类的子类ImageViewManager类,其他管理类相似,下面贴出本类全部代码,是不是非常清晰:

public class ImageViewManager extends BaseViewHolderManager<ImageBean> {

    @Override
    public void onBindViewHolder(BaseViewHolder holder, ImageBean data) {
        //在指定viewHolder中获取控件为id的view
        ImageView imageView = getView(holder, R.id.image);
        imageView.setImageResource(data.getImg());
    }

    @Override
    protected int getItemLayoutId() {
        //返回item布局文件id
        return R.layout.item_image;
    }
}

相同数据源对应多个ViewHolder(聊天界面)

这是一种特殊的需求,需要在运行时通过数据源中的某个属性,判断加载的布局,典型的就是聊天功能,相同消息数据对应左右两种气泡视图,在此处贴出注册时的关键代码,其他和多种类型列表类似:

//初始化adapter
BaseItemAdapter adapter = new BaseItemAdapter();

//为XXBean数据源注册XXManager管理类组合
adapter.register(MessageBean.class, new ViewHolderManagerGroup<MessageBean>(new SendMessageManager(), new ReceiveMessageManager()) {
    @Override
    public int getViewHolderManagerIndex(MessageBean itemData) {
        //根据message判断是否本人发送并返回对应ViewHolderManager的index值
        return itemData.getSender().equals(uid) ? 0 : 1;
    }
});

recyclerView.setAdapter(adapter);

设置点击监听

点击监听:

adapter.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(BaseViewHolder viewHolder) {
        //通过viewHolder获取需要的数据
        toastUser(String.format("你点击了第%s位置的数据:%s", viewHolder.getItemPosition()
        , viewHolder.getItemData()));
    }
});

长按监听:

adapter.setOnItemLongClickListener(new OnItemLongClickListener() {
    @Override
    public void onItemLongClick(BaseViewHolder viewHolder) {
        //通过viewHolder获取需要的数据
        toastUser(String.format("你长按了第%s位置的数据:%s", viewHolder.getItemPosition()
                , viewHolder.getItemData()));
    }
});

为列表添加header和footer

添加header footer提供两种方式,直接addView或者addItem方式:

//为XXBean数据源注册XXManager管理类
adapter.register(TextBean.class, new TextViewManager());

//添加header
TextView headView = new TextView(this);
headView.setText("通过addHeadView增加的head1");
//方式一:方便实际业务使用
adapter.addHeadView(headView);
//方式二:这种方式和直接addDataItem添加数据源原理一样
adapter.addHeadItem(new TextBean("通过addHeadItem增加的head2"));

//添加footer,方式同添加header
TextView footView = new TextView(this);
footView.setText("通过addFootView增加的foot1");
adapter.addFootView(footView);
adapter.addFootItem(new TextBean("通过addFootItem增加的foot2"));

为表格添加充满宽度的Item(含header和footer)

充满宽度详见ViewHolderManager#isFullSpan返回true即可,适用于head foot或任意数据源Item

//此处为TextBean数据源注册FullSpanTextViewManager管理类
adapter.register(TextBean.class, new FullSpanTextViewManager());

//添加header或者footer
TextView headView = new TextView(this);
headView.setText("通过addHeadView增加的head1");
//使用HeadFootHolderManager已经实现isFullSpan方法,默认充满宽度
adapter.addHeadView(headView);

//添加普通Item,详见FullSpanTextViewManager,默认充满宽度
adapter.addDataItem(new TextBean("FullSpanTextViewManager充满宽度Item"));

下拉刷新加载更多功能的用法

下拉刷新采用SwipeRefreshLayout这里就不在过多介绍,开启和处理加载更多功能比较简单,但是需要注意加载更多本质上是一个footer,并且对添加顺序敏感,所以需要先去addFoot后在调用开启方法:

//开启加载更多视图
adapter.enableLoadMore(new LoadMoreHolderManager(this::loadData));

//加载完成 isLoadAll:是否全部数据
adapter.setLoadCompleted(boolean isLoadAll);

//加载失败
adapter.setLoadFailed();

通过开启方法我们可以看出依赖于LoadMoreHolderManager,主要是处理不同状态下加载更多界面的变化,下面贴出代码,更多实现细节请参阅LoadMoreManager

/**
 * 加载更多视图管理类
 */
public class LoadMoreHolderManager extends LoadMoreManager {

    public LoadMoreHolderManager(OnLoadMoreListener onLoadMoreListener, boolean isAutoLoadMore) {
        super(onLoadMoreListener, isAutoLoadMore);
    }

    @Override
    protected int getItemLayoutId() {
        return R.layout.item_load_more;
    }

    @Override
    protected void updateLoadInitView() {
        ((TextView) getView(loadMoreView, R.id.text)).setText("");
    }

    @Override
    protected void updateLoadingMoreView() {
        ((TextView) getView(loadMoreView, R.id.text)).setText(R.string.loading_more);
    }

    @Override
    protected void updateLoadCompletedView(boolean isLoadAll) {
        ((TextView) getView(loadMoreView, R.id.text))
                .setText(isLoadAll ? R.string.load_all : R.string.load_has_more);
    }

    @Override
    protected void updateLoadFailedView() {
        ((TextView) getView(loadMoreView, R.id.text)).setText(R.string.load_failed);
    }
}

开启滑动动画

//开启动画,并取消动画只在第一次加载时展示
adapter.enableAnimation(baseAnimation, false);

下面写下动画相关方法,库中已经默认实现了部分BaseAnimation,可以通过Demo看到具体效果

 /**
 * 启动加载动画
 *
 * @param animation    BaseAnimation
 * @param isShowAnimWhenFirstLoad boolean 是否只有在第一次展示的时候才使用动画
 */
public void enableAnimation(BaseAnimation animation, boolean isShowAnimWhenFirstLoad)

/**
 * 设置动画时长 默认400L
 */
public void setAnimDuration(long animDuration)

/**
 * 设置动画加速器 默认{@link LinearInterpolator}
 */
public void setInterpolator(@NonNull Interpolator interpolator)

至此本库的多种类型列表用法已经完成,并没有修改或继承RecyclerView Adapter类,完全使用默认实现BaseItemAdapter即可。

详解

主要流程

  • 为指定的数据源注册ViewHolderManager提供视图创建绑定等工作
  • 在列表创建的过程中通过数据源在ItemTypeManager找到对应的ViewHolderManager
  • 按照需要创建与刷新视图并对视图做一些通用处理

ViewHolder管理

ViewHolder管理源码类为ViewHolderManager,使用者会首先注册数据源和本实例的对应关系,由类型管理类提供统一管理。

  • 提供了参数类,会在adapter调用本类方法的时候传入并做出通用处理
  • 本类的设计使用泛型,是为了在后续回调方法中有更直观的类型体现,这也是强类型和泛型带来的好处,给人在编写代码的时候带来确定感
  • 本类为抽象类需要重写ViewHolder的创建与绑定方法,为了方便后续使用,写了一个简单的BaseViewHolderManager实现类,请读者根据业务自行决定是否需要使用更灵活的基类,这里贴出需要复写的两个方法,延续了Adapter中的命名规则,在使用中减少一些认知成本:
/**
 * 创建ViewHolder
 * {@link android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder}
 */
@NonNull
public abstract V onCreateViewHolder(@NonNull ViewGroup parent);

/**
 * 为ViewHolder绑定数据
 * {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder}
 *
 * @param t 数据源
 */
public abstract void onBindViewHolder(@NonNull V holder, @NonNull T t);

ViewHolder管理组合(相同数据源对应多个ViewHolderManager)

组合管理源码类为ViewHolderManagerGroup,本实例需要一个ViewHolderManager集合,并增加通过数据源指定哪个ViewHolderManager的方法,使用者同样会注册数据源和本实例的对应关系,由类型管理类对本类中的ViewHolderManager集合进行统一注册管理。下面贴出关键代码:

 private ViewHolderManager[] viewHolderManagers;

/**
 * @param viewHolderManagers 相同数据源对应的所有ViewHolderManager
 */
public ViewHolderManagerGroup(ViewHolderManager... viewHolderManagers) {
    if (viewHolderManagers == null || viewHolderManagers.length == 0) {
        throw new IllegalArgumentException("viewHolderManagers can not be null");
    }
    this.viewHolderManagers = viewHolderManagers;
}

/**
 * 根据item数据源中的属性判断应该返回的对应viewHolderManagers的index值
 *
 * @param itemData item数据源
 * @return index值应该是在viewHolderManagers数组有效范围内
 */
public abstract int getViewHolderManagerIndex(T itemData);

类型管理

类型管理源码类为ItemTypeManager,通过数据源className ListviewHolderManager List两组集合对类型进行管理,并对Adapter提供注册和对应关系查找等方法的支持,这里并没有把这个地方设计灵活,如果有一些变化还是希望可以在ViewHolderManager做出适配。

  • 数据源一对一viewHolderManager时比较简单,关键代码:
 /**
 * 通过数据源`className List`和`viewHolderManager List`两组集合对类型进行管理
 *
 * @param cls     数据源class
 * @param manager ViewHolderManager
 * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManager)
 */
public void register(Class<?> cls, ViewHolderManager manager) {
    register(getClassName(cls), manager);
}
  • 数据源一对多viewHolderManager时,关键代码:
/**
 * 通过group获取一组ViewHolderManager循环注册,并生成不同的className作为标识<br>
 * 其他类似{@link #register(Class, ViewHolderManager)}
 *
 * @param cls   数据源class
 * @param group 多个ViewHolderManager的组合
 * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManagerGroup)
 */
public void register(Class<?> cls, ViewHolderManagerGroup group) {
    ViewHolderManager[] managers = group.getViewHolderManagers();
    for (int i = 0, length = managers.length; i < length; i++) {
        register(getClassNameFromGroup(cls, group, managers[i]), managers[i]);
    }
    itemClassNameGroupMap.put(getClassName(cls), group);
}

感谢

在编写中感谢以下开源项目提供了很多思路

multiitem's People

Contributors

free46000 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

multiitem's Issues

项目运行不了

Error:A problem occurred configuring project ':demo'.

Could not resolve all dependencies for configuration ':demo:_debugApkCopy'.
Could not find com.android.support.constraint:constraint-layout:1.0.1.
Required by:
project :demo

android studio 3.0导入项目出现如下错误

Information:Gradle tasks [:demo:assembleDebug]
Warning:One of the plugins you are using supports Java 8 language features. To try the support built into the Android plugin, remove the following from your build.gradle:
    apply plugin: 'me.tatarka.retrolambda'
To learn more, go to https://d.android.com/r/tools/java-8-support-message.html

Warning:One of the plugins you are using supports Java 8 language features. To try the support built into the Android plugin, remove the following from your build.gradle:
    apply plugin: 'me.tatarka.retrolambda'
To learn more, go to https://d.android.com/r/tools/java-8-support-message.html

Error:Execution failed for task ':library:transformClassesWithRetrolambdaForDebug'.
> Missing javaCompileTask for variant: debug/0 from output dir: /Users/dev01/Downloads/MultiItem-master/library/build/intermediates/transforms/retrolambda/debug/0

触发滑动的条件

当拖拽的view,向右滑动时,只要拖拽的view超出屏幕时就开始滑动,这个该怎么实现

跨区拖拽怎么改变item值

设置数据源设置位置标志flag,拖拽位置改变,位置标志怎么改成相对应的? 比如 item在第一列是 flag= 1,拖拽到第二列第3位置,f需要flag=3,怎么处理?

java.lang.NullPointerException

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
at com.freelib.multiitem.item.LoadMoreManager.setOnLoadMoreClickListener(LoadMoreManager.java:57)
at com.freelib.multiitem.item.LoadMoreManager.loadCompleted(LoadMoreManager.java:115)
at com.freelib.multiitem.adapter.BaseItemAdapter.setLoadCompleted(BaseItemAdapter.java:255)
at com.pd.mainweiyue.view.activity.ReadActivity$20.postSuccessful(ReadActivity.java:1574)
at com.pd.mainweiyue.net.OkHttpUtils$1$4.run(OkHttpUtils.java:201)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

数据错位

我继承了BaseViewHolderManager ,然后在onBindViewHolder 里启动了一个CountDownTimer做倒计时处理,因为每个数据的时间都不一样,item复用导致 时间异常,这个怎么解决

ViewHolderManager能否提供视图销毁方法

目前ViewHolderManager仅提供了绑定数据方法onBindViewHolder,能否也提供视图销毁方法,比如如果要在BaseViewHolderManager中异步加载网络图像的话,就需要在视图销毁时能及时停止并销毁异步加载进程,或者有什么其他解决方案吗?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.