OneAdapter: RecyclerView最简单的万能适配器

之前写过一篇使用RecyclerView,一句代码就够了,介绍了一个功能较完善的RecyclerView框架的实现。该框架虽然代码不多,但是仍然不够简洁,耦合度也比较高,难以扩展。现将里面的核心部分 OneAdapter 抽取出来,去掉不必要的泛型、类型判断和其他方法,以实现最简单、通用性和扩展性最好的Adapter。

OneAdapter: RecyclerView最简单的万能适配器_第1张图片
ComplexList.png

在Github上搜索adatper,选Java语言,有5K+的记录,主要也都是RecyclerView或ListView的适配器封装。既然已经有这么多实现在先,这里再实现一遍有意义吗?

有的,这里的实现是最简单、代码最少的。

OneAdapter代码如下:

/**
 * A custom adapter, supports multi-ItemViewType
 * 
 * Created by rome753 on 2018/2/1.
 */
public class OneAdapter extends RecyclerView.Adapter {

    private final List data;
    private final List listeners;

    public OneAdapter(OneListener... listeners) {
        this.data = new ArrayList<>();
        this.listeners = new ArrayList<>();
        this.listeners.addAll(Arrays.asList(listeners));
    }

    public void setData(List data) {
        this.data.clear();
        this.data.addAll(data);
    }

    public void addData(List data) {
        this.data.addAll(data);
    }

    public List getData() {
        return data;
    }

    public List getListeners() {
        return listeners;
    }

    @Override
    public int getItemViewType(int position) {
        Object o = data.get(position);
        for (int i = 0; i < listeners.size(); i++) {
            OneListener listener = listeners.get(i);
            if (listener.isMyItemViewType(position, o)) {
                return i;
            }
        }
        return 0;
    }

    @Override
    public OneViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return listeners.get(viewType).getMyViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(OneViewHolder holder, int position) {
        Object o = data.get(position);
        holder.bindView(position, o);
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

}
 
 

OneAdatper继承自RecyclerView.Adapter,重写了4个方法,并在其中增加了两个List,核心代码只有几行,在 getItemViewType(int position) 这个方法中。

原因

很多同学开发时看到有列表就来一个RecyclerView,然后又实现一个Adapter。这两步都是没有必要的。

先说第一步,RecyclerView并不是有列表就使用的。Recycle的意思是回收,也就是说,只有在需要回收时才使用。什么时候需要回收呢?列表数据项很多或者单个数据项占内存很大时。其他情况下,比如类似微信的设置页面那种简单的列表,不需要回收,用ScrollView实现就可以了,代码更简单,性能更好。这应该也是Google让开发者从ListView迁移到RecyclerView的目的。

再说第二步,每个RecyclerView实现一个Adapter也是冗余的。Adapter的本质是控制列表中每一项的视图(View)与数据(Data)的对应关系,所以它应该只做一件事:RecyclerView把某一项视图传过来时,Adapter把数据传给视图。然而现在Adapter中处理了数据类型和视图类型,这导致它跟具体业务耦合度很高,尤其是数据类型和视图类型多样时。

举个例子:

    class MyAdapter extends BaseAdapter> {

        private final int TYPE_HEADER = 0;
        private final int TYPE_COINS = 1;

        @Override
        public int getItemCount() {
            return mData.size();
        }

        @Override
        public BaseHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_HEADER) {
                return new BaseHolder(new CoinNumberView(parent.getContext()));
            } else {
                return new BaseHolder(new BuyCoinsView(parent.getContext(), BuyCoinActivity.this));
            }
        }

        @Override
        public void onBindViewHolder(BaseHolder holder, int position) {
            if (holder.itemView instanceof BuyCoinsView) {
                BuyCoinsView buyCoinsView = (BuyCoinsView) holder.bindView;
                buyCoinsView.bindDataByPosition(mData.get(position), position);
            } else if (holder.itemView instanceof CoinNumberView) {
                CoinNumberView coinNumberView = (CoinNumberView) holder.bindView;
                coinNumberView.bindData(null);
            }
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return TYPE_HEADER;
            } else {
                return TYPE_COINS;
            }
        }
    }

这里为了给列表增加一个Header,在Adapter中增加一个类型,然后不得不用 if...else... 或者 switch 语句判断ItemViewType的类型、ViewHolder的类型和ItemView的类型。

Adapter依赖所有类型的所有对象,画图来看是这样的:

OneAdapter: RecyclerView最简单的万能适配器_第2张图片
adapter.png

这里只是两种类型,如果有4,5种乃至7,8种,那么Adapter就爆炸了!

原理

OneAdapter解决了Adapter的过度耦合问题,它只依赖OneListener和OneViewHolder这两个类,只关联List这一个对象,其他所有依赖关系都被List转移到外部了。如图所示:

OneAdapter: RecyclerView最简单的万能适配器_第3张图片
oneadapter.png

无论有多少种数据类型,都只需要在外部实现OneListener和OneViewHolder,给OneAdapter传入OneListener列表即可。

OneAdapter不依赖具体的数据类型,使用Object表示数据类型,而不是泛型。这样做是因为泛型一般针对一种或固定几种不确定的类型,而Adapter中不但有多种不确定的类型、而且具体有几种也是不固定的,因此无法使用泛型。为了传入数据不限制于Object类型,在OneAdapter中的 setData(List data) 方法参数使用了泛型的不确定类型。

OneListener代码如下:

/**
 * A listener for: define item view type and create ViewHolder, outside of the adapter
 */
public interface OneListener{

    /**
     * Is the position or the data suits for this OneListener?
     * @param position the data's position int the list
     * @param o the data
     * @return true/false
     */
    boolean isMyItemViewType(int position, Object o);

    /**
     * Create a ViewHolder for this OneListener
     * @param parent RecyclerView
     * @return OneViewHolder
     */
    OneViewHolder getMyViewHolder(ViewGroup parent);
}

OneListener是一个接口,它建立了列表中具体位置、具体数据与具体OneViewHolder的对应关系。实际上每个OneListener实例表示列表中一种条目类型。它里面有两个方法。

  • isMyItemViewType(int position, Object o) 方法让实现者根据位置或者该位置的数据判断是不是当前OneListener对应的条目类型。
  • getMyViewHolder(ViewGroup parent) 方法让实现者实现当前OneListener对应的OneViewHolder子类。

OneListener也不依赖具体的数据类型,因为判断条目类型并不一定是根据数据类型判断,也可能根据位置判断。这给了调用者最大的灵活度。虽然OneViewHolder有泛型,但是OneListener并不需要关心。

OneViewHolder代码如下:

/**
 * A ViewHolder that auto cast the data, from Object to the type you define
 * @param  the data type you define
 */
public abstract class OneViewHolder extends RecyclerView.ViewHolder {

    public OneViewHolder(View itemView) {
        super(itemView);
    }

    public OneViewHolder(ViewGroup parent, int layoutRes) {
        super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
    }

    void bindView(int position, Object o){
        bindViewCasted(position, (D) o);
    }

    protected abstract void bindViewCasted(int position, D d);
}

OneViewHolder继承自RecyclerView.ViewHolder,它将具体数据与视图绑定。由于数据在OneAdapter中都是Object类型,为了调用者方便,这里利用泛型自动对数据进行了强制类型转换。至于绑定视图封装了两个方法:

  • OneViewHolder(ViewGroup parent, int layoutRes) 方法用于直接传入ItemView的布局资源,用于大多数情况。
  • OneViewHolder(View itemView) 方法用于ItemView是自定义View的情况(此时要注意自定义View的LayoutParams)。

到这里,主要代码就讲完了。

示例

  1. 简单列表


    OneAdapter: RecyclerView最简单的万能适配器_第4张图片
    SimpleList.png
public class SimpleListActivity extends AppCompatActivity {

    OneAdapter oneAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        oneAdapter = new OneAdapter(new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return true;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolder(parent, R.layout.item_text){

                    @Override
                    protected void bindViewCasted(int position, String s) {
                        TextView text = itemView.findViewById(R.id.text);
                        text.setText(s);
                    }
                };
            }
        });

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        requestData();
    }

    private void requestData() {
        List data = new ArrayList<>();
        for(int i = 'A'; i <= 'z'; i++) {
            data.add(" " + (char)i);
        }
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}
  1. 带Header和Footer的列表
OneAdapter: RecyclerView最简单的万能适配器_第5张图片
HeaderFooter.png
public class HeaderFooterActivity extends AppCompatActivity {
    RecyclerView recyclerView;
    OneAdapter oneAdapter;
    View footerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        oneAdapter = new OneAdapter(
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return position == 0;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder(parent, R.layout.item_text) {

                            @Override
                            protected void bindViewCasted(int position, Object o) {
                                TextView text = itemView.findViewById(R.id.text);
                                text.setText("This is header");
                            }
                        };
                    }
                },
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return o instanceof String;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder(parent, android.R.layout.simple_list_item_1) {

                            @Override
                            protected void bindViewCasted(int position, String s) {
                                TextView text = itemView.findViewById(android.R.id.text1);
                                text.setText(s);
                            }
                        };
                    }
                },
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return position == oneAdapter.getItemCount() - 1;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder(footerView) {

                            @Override
                            protected void bindViewCasted(int position, Object o) {
                            }
                        };
                    }
                }
        );

        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        initFooterView();
        requestData();
    }

    private void initFooterView() {
        footerView = LayoutInflater.from(this).inflate(R.layout.item_text, recyclerView, false);
        ((TextView)footerView.findViewById(R.id.text)).setText("This is footer");
    }

    private void requestData() {
        List data = new ArrayList<>();
        data.add(null);
        for (int i = 'A'; i <= 'Z'; i++) {
            data.add(" " + (char) i);
        }
        data.add(null);
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}
 
 

扩展:Databinding支持

Databinding是Google推荐的做法,有了它就不需要写 findViewById() 语句了,还能直接在Layout文件中绑定数据。 使用方法也很简单,大家可以自己查一下相关教程。这里给OneAdapter添加Databinding支持。

对于OneAdapter来说,Databinding主要用于具体数据与视图绑定,也就是OneViewHolder中所做的。OneViewHolder有两个构造方法,分别对应自定义View和布局资源文件。对于自定义View来说,是否使用Databinding是调用者自己控制的。因此Databinding支持是针对使用布局资源文件的情况,这里封装了一个包装类OneViewHolderWrapper,用它替换OneViewHolder即可。

OneViewHolderWrapper代码:

/**
 * A wrapper of OneViewHolder, supports data binding
 * @param  the type of the data
 * @param  the type of the ViewDataBinding
 */
public abstract class OneViewHolderWrapper{

    private OneViewHolder oneViewHolder;

    protected B binding;

    public OneViewHolderWrapper(ViewGroup parent, int layoutRes){
        binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutRes, parent, false);
        oneViewHolder = new OneViewHolder(binding.getRoot()) {
            @Override
            protected void bindViewCasted(int position, D d) {
                OneViewHolderWrapper.this.bindViewCasted(position, d);
            }
        };
    }

    public OneViewHolder getOneViewHolder() {
        return oneViewHolder;
    }

    protected abstract void bindViewCasted(int position, D d);
}

OneViewHolderWrapper中用D表示数据泛型,B表示ViewDataBinding泛型。binding对象用于绑定具体数据。

实际使用代码:

public class DataBindingActivity extends AppCompatActivity {

    OneAdapter oneAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        oneAdapter = new OneAdapter(new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return true;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolderWrapper(parent, R.layout.item_person) {
                    @Override
                    protected void bindViewCasted(int position, Person person) {
                        binding.setPerson(person);
                        binding.executePendingBindings();
                    }
                }.getOneViewHolder();
            }
        });

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        requestData();
    }

    private void requestData() {
        List data = new ArrayList<>();
        for(int i = 0; i <= 10; i++) {
            data.add(new Person("Bill", 22));
            data.add(new Person("Chris", 10));
            data.add(new Person("David", 36));
        }
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}
 
 

需要OneViewHolder实例时,先创建包装类OneViewHolderWrapper实例,然后调用 getOneViewHolder() 方法从包装类中取得OneViewHolder实例。这样原有的OneAdapter和OneListener都直接兼容。

OneAdapter: RecyclerView最简单的万能适配器_第6张图片
Databinding.png

扩展:下拉刷新和加载更多

用SwipeRefreshLayout和FooterView实现了简单的下拉刷新和加载更多功能,这是对OneAdapter的简单扩展。没有加入EmptyView,因为EmptyView可以完全在外部控制。

public class RecyclerLayout extends SwipeRefreshLayout implements OnRefreshListener, LoadingLayout.OnLoadingListener {

    private RecyclerView recyclerView;
    private LoadingLayout loadingLayout;

    private OneAdapter oneAdapter;
    private GridLayoutManager gridLayoutManager;

    private OnRefreshListener onRefreshListener;
    private LoadingLayout.OnLoadingListener onLoadingListener;

    public RecyclerLayout(Context context) {
        this(context, null);
    }

    public RecyclerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnRefreshListener(this);

        loadingLayout = new LoadingLayout(context);

        gridLayoutManager = new GridLayoutManager(context, 1);
        recyclerView = new RecyclerView(context);
        recyclerView.setLayoutManager(gridLayoutManager);
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            int lastVisibleItemPosition;

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == oneAdapter.getItemCount() - 1 - 1) {
                    // load more
                    onLoading();
                }
            }
        });

        addView(recyclerView);
    }

    public void init(final OneAdapter oneAdapter, OnRefreshListener onRefreshListener, LoadingLayout.OnLoadingListener onLoadingListener){
        this.recyclerView.setAdapter(oneAdapter);
        this.oneAdapter = oneAdapter;
        this.oneAdapter.getListeners().add(0, new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return position == oneAdapter.getItemCount() - 1;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolder(loadingLayout) {
                    @Override
                    protected void bindViewCasted(int position, Object o) {
                        //ignore
                    }
                };
            }
        });

        if(onRefreshListener == null){
            setEnabled(false);
        }
        this.onRefreshListener = onRefreshListener;
        this.onLoadingListener = onLoadingListener;
    }

    @Override
    public void onRefresh() {
        if(onRefreshListener != null){
            onRefreshListener.onRefresh();
        }
    }

    @Override
    public void onLoading() {
        if(onLoadingListener != null && !isRefreshing() && !isLoading() && !isNoMore()){
            onLoadingListener.onLoading();
            setLoading(true, isNoMore());
        }
    }

    public void setData(List data, boolean hasMore){
        data.add(null);
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();

        setRefreshing(false);
        setLoading(false, !hasMore);
    }

    public void addData(List data, boolean hasMore){
        List cur = oneAdapter.getData();
        if(!cur.isEmpty()){
            cur.remove(cur.size() - 1);
        }
        data.add(null);
        oneAdapter.addData(data);
        oneAdapter.notifyDataSetChanged();

        setLoading(false, !hasMore);
    }

    private boolean isNoMore(){
        return loadingLayout.isNoMore();
    }

    private boolean isLoading(){
        return loadingLayout.isLoading();
    }

    private void setLoading(boolean loading, boolean isNoMore){
        loadingLayout.setLoading(loading, isNoMore);
    }
}
 
 

实际使用如下:

public class RefreshActivity extends AppCompatActivity {

    RecyclerLayout recyclerLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        recyclerLayout = new RecyclerLayout(this);
        setContentView(recyclerLayout);

        OneAdapter oneAdapter = new OneAdapter(
                new OneListener() {

                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return true;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {

                        return new OneViewHolder(parent, R.layout.item_text) {
                            @Override
                            protected void bindViewCasted(int position, String s) {
                                TextView text = itemView.findViewById(R.id.text);
                                text.setText(s);
                            }
                        };
                    }
                }
        );

        recyclerLayout.init(oneAdapter,
                new SwipeRefreshLayout.OnRefreshListener() {
                    @Override
                    public void onRefresh() {
                        requestData();
                    }
                },
                new LoadingLayout.OnLoadingListener() {
                    @Override
                    public void onLoading() {
                        requestMoreData();
                    }
                }
        );


        recyclerLayout.setRefreshing(true);
        requestData();
    }

    int page;

    private void requestData() {
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {

                List data = new ArrayList<>();
                for (int i = 'A'; i <= 'Z'; i++) {
                    String s = (char) i + " " + System.nanoTime();
                    data.add(s);
                }

                page = 0;
                recyclerLayout.setData(data, page++ < 2);

            }
        }, 1000);
    }

    private void requestMoreData() {
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {

                List data = new ArrayList<>();
                for (int i = 'A'; i <= 'Z'; i++) {
                    String s = (char) i + " " + System.nanoTime();
                    data.add(s);
                }

                recyclerLayout.addData(data, page++ < 2);

            }
        }, 1000);
    }

}

 
 

代码结构

OneAdapter: RecyclerView最简单的万能适配器_第7张图片
oneadapter.png
  • 实现普通或多种类型的RecyclerView,使用base包中的类即可;
  • 如果需要Databinding支持,加入databinding包中的类;
  • 如果需要下拉刷新和加载更多,可以参考refresh包中的实现。

Github地址:https://github.com/rome753/OneAdapter
完整代码和Demo示例都在这里,欢迎Fork和Star哦。

你可能感兴趣的:(OneAdapter: RecyclerView最简单的万能适配器)