ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-19 20:33:29  阅读:214  来源: 互联网

标签:OneViewHolder OneAdapter -- 适配器 int position Override new public


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

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<OneViewHolder> {

    private final List<Object> data;
    private final List<OneListener> 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<Object> getData() {
        return data;
    }

    public List<OneListener> 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<SkuItem, BaseHolder<BaseView>> {

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

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

        @Override
        public BaseHolder<BaseView> 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<BaseView> 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依赖所有类型的所有对象,画图来看是这样的:

adapter.png

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

原理

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

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 <D> the data type you define
 */
public abstract class OneViewHolder<D> 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. 简单列表
    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<String>(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<String> data = new ArrayList<>();
        for(int i = 'A'; i <= 'z'; i++) {
            data.add(" " + (char)i);
        }
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}
  1. 带Header和Footer的列表

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<Object>(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<String>(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<Object>(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<Object> 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 <D> the type of the data
 * @param <B> the type of the ViewDataBinding
 */
public abstract class OneViewHolderWrapper<D,B extends ViewDataBinding>{

    private OneViewHolder<D> oneViewHolder;

    protected B binding;

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

    public OneViewHolder<D> 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<Person, ItemPersonBinding>(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<Object> 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都直接兼容。
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 onl oadingListener;

    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
                    onl oading();
                }
            }
        });

        addView(recyclerView);
    }

    public void init(final OneAdapter oneAdapter, OnRefreshListener onRefreshListener, LoadingLayout.OnLoadingListener onl oadingListener){
        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 = onl oadingListener;
    }

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

    @Override
    public void onl oading() {
        if(onLoadingListener != null && !isRefreshing() && !isLoading() && !isNoMore()){
            onl oadingListener.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<Object> 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<String>(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 onl oading() {
                        requestMoreData();
                    }
                }
        );


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

    int page;

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

                List<Object> 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<Object> 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.png

  • 实现普通或多种类型的RecyclerView,使用base包中的类即可;
  • 如果需要Databinding支持,加入databinding包中的类;
  • 如果需要下拉刷新和加载更多,可以参考refresh包中的实现。

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

标签:OneViewHolder,OneAdapter,--,适配器,int,position,Override,new,public
来源: https://www.cnblogs.com/rome753/p/16495617.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有