Android 解析RecyclerView(3)——以更简单的方法实现带顶部View和底部View的RecyclerView

在上一篇文章:解析RecyclerView(2)——带顶部View和底部View的RecyclerView ,我介绍了如何为RecyclerView 添加头部View和底部View,功能虽然实现了,不过需要用到的类也相对较多。其实可以像我在第一篇文章里介绍的使用泛型来适应不同情况那样,同样可以在 WrapRecyclerViewAdapter 中使用泛型,这样会使代码更为简洁

如果看过我前两篇文章了,以下代码应该就很容易理解了

一、通用ViewHolder

/**
 * 通用ViewHolder
 * Created by ZY on 2017/6/3.
 */
public class GeneralWrapRecyclerHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

    public interface onClickCommonListener {

        void onClick(int position);

        void onLongClick(int position);

    }
    
    private onClickCommonListener clickCommonListener;

    //用来存放View以减少findViewById的次数
    private SparseArray<View> viewSparseArray;

    public GeneralWrapRecyclerHolder(View itemView) {
        super(itemView);
        viewSparseArray = new SparseArray<>();
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
    }

    public void setClickCommonListener(onClickCommonListener clickCommonListener) {
        this.clickCommonListener = clickCommonListener;
    }

    @Override
    public void onClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onClick(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onLongClick(getAdapterPosition());
        }
        return true;
    }

    /**
     * 根据 ID 来获取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    private <T extends View> T getView(int viewId) {
        // 先从缓存中找,找到的话则直接返回
        // 如果找不到则findViewById,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

    public GeneralWrapRecyclerHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    public GeneralWrapRecyclerHolder setImageResource(int viewId, int resourceId) {
        ImageView imageView = getView(viewId);
        imageView.setImageResource(resourceId);
        return this;
    }

    public GeneralWrapRecyclerHolder setImageResource(int viewId, Bitmap bitmap) {
        ImageView imageView = getView(viewId);
        imageView.setImageBitmap(bitmap);
        return this;
    }

    public GeneralWrapRecyclerHolder setViewVisibility(int viewId, int visibility) {
        getView(viewId).setVisibility(visibility);
        return this;
    }

}

二、GeneralWrapRecyclerAdapter

/**
 * 可以带头部View与尾部View的RecyclerView Adapter
 * Created by CZY on 2017/6/4.
 */
public abstract class GeneralWrapRecyclerAdapter<T> extends RecyclerView.Adapter<GeneralWrapRecyclerHolder> {

    public interface MultiTypeSupport<T> {

        int getLayoutId(T item, int position);

    }

    private LayoutInflater layoutInflater;

    private List<T> dataList;

    private int layoutId;

    private MultiTypeSupport<T> multiTypeSupport;

    private GeneralWrapRecyclerHolder.onClickCommonListener clickCommonListener;

    private SparseArray<View> headerViews;

    private SparseArray<View> footerViews;

    //头部类型开始位置,用于viewType
    private int BASE_ITEM_TYPE_HEADER = 1000;

    //底部类型开始位置,用于viewType
    private int BASE_ITEM_TYPE_FOOTER = 2000;

    /**
     * 私有构造函数
     *
     * @param context  上下文
     * @param dataList 数据集合
     */
    private GeneralWrapRecyclerAdapter(Context context, List<T> dataList) {
        this.layoutInflater = LayoutInflater.from(context);
        this.dataList = dataList;
        headerViews = new SparseArray<>();
        footerViews = new SparseArray<>();
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且不需要监听点击事件
     *
     * @param context  上下文
     * @param dataList 数据集合
     * @param layoutId 布局文件ID
     */
    protected GeneralWrapRecyclerAdapter(Context context, List<T> dataList, int layoutId) {
        this(context, dataList);
        this.layoutId = layoutId;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且不需要监听点击事件
     *
     * @param context          上下文
     * @param dataList         数据集合
     * @param multiTypeSupport 支持多个布局文件
     */
    protected GeneralWrapRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport) {
        this(context, dataList);
        this.multiTypeSupport = multiTypeSupport;
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param layoutId            布局文件ID
     * @param clickCommonListener 点击事件监听
     */
    protected GeneralWrapRecyclerAdapter(Context context, List<T> dataList, int layoutId,
                                         GeneralWrapRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, layoutId);
        this.clickCommonListener = clickCommonListener;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param multiTypeSupport    支持多个布局文件
     * @param clickCommonListener 点击事件监听
     */
    protected GeneralWrapRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport,
                                         GeneralWrapRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, multiTypeSupport);
        this.clickCommonListener = clickCommonListener;
    }

    /**
     * 根据索引判断该位置的View类型
     * 如果是头部,则返回该View在headerViews中的key
     * 如果是底部,则返回该View在footerViews中的key
     *
     * @param position 索引
     * @return View类型
     */
    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            return headerViews.keyAt(position);
        }
        if (isFooterPosition(position)) {
            position = position - headerViews.size() - getDataItemCount();
            return footerViews.keyAt(position);
        }
        position = position - headerViews.size();
        if (multiTypeSupport != null) {
            return multiTypeSupport.getLayoutId(dataList.get(position), position);
        }
        return super.getItemViewType(position);
    }

    @Override
    public GeneralWrapRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (isHeaderViewType(viewType)) {
            return createHeaderFooterViewHolder(headerViews.get(viewType));
        }
        if (isFooterViewType(viewType)) {
            return createHeaderFooterViewHolder(footerViews.get(viewType));
        }
        if (multiTypeSupport != null) {
            layoutId = viewType;
        }
        View view = layoutInflater.inflate(layoutId, parent, false);
        return new GeneralWrapRecyclerHolder(view);
    }

    @Override
    public void onBindViewHolder(GeneralWrapRecyclerHolder holder, int position) {
        holder.setClickCommonListener(clickCommonListener);
        if (isHeaderPosition(position) || isFooterPosition(position)) {
            return;
        }
        bindData(holder, dataList.get(position - getHeaderItemCount()));
    }

    /**
     * 获取列表总的条数(头部View个数+列表条数+底部View个数)
     *
     * @return 总的条数
     */
    @Override
    public int getItemCount() {
        return dataList.size() + headerViews.size() + footerViews.size();
    }

    /**
     * 获取不包含头部和底部View之后列表的条数
     *
     * @return 列表条数
     */
    private int getDataItemCount() {
        return dataList.size();
    }

    protected abstract void bindData(GeneralWrapRecyclerHolder holder, T data);

    /**
     * 获取头部View数量
     *
     * @return 头部View数量
     */
    private int getHeaderItemCount() {
        return headerViews.size();
    }

    /**
     * 创建头部View或底部View的ViewHolder
     *
     * @param view 头部View或底部View
     * @return ViewHolder
     */
    private GeneralWrapRecyclerHolder createHeaderFooterViewHolder(View view) {
        return new GeneralWrapRecyclerHolder(view) {
        };
    }

    /**
     * 判断是否是头部View
     *
     * @param key Key
     * @return 是否是头部View
     */
    private boolean isHeaderViewType(int key) {
        return headerViews.indexOfKey(key) > -1;
    }

    /**
     * 判断是否是底部View
     *
     * @param key Key
     * @return 是否是底部View
     */
    private boolean isFooterViewType(int key) {
        return footerViews.indexOfKey(key) > -1;
    }

    /**
     * 根据索引判断该位置的View是否是头部View
     *
     * @param position 索引
     * @return 是否是头部View
     */
    private boolean isHeaderPosition(int position) {
        return (position > -1) && (position < headerViews.size());
    }

    /**
     * 根据索引判断该位置的View是否是底部View
     *
     * @param position 索引
     * @return 是否是底部View
     */
    private boolean isFooterPosition(int position) {
        return (position >= (headerViews.size() + getDataItemCount())) &&
                (position < (headerViews.size() + getDataItemCount() + footerViews.size()));
    }

    /**
     * 添加头部View
     *
     * @param view 头部View
     */
    public void addHeaderView(View view) {
        if (headerViews.indexOfValue(view) < 0) {
            headerViews.put(BASE_ITEM_TYPE_HEADER++, view);
            notifyDataSetChanged();
        }
    }

    /**
     * 添加底部View
     *
     * @param view 底部View
     */
    public void addFooterView(View view) {
        if (footerViews.indexOfValue(view) < 0) {
            footerViews.put(BASE_ITEM_TYPE_FOOTER++, view);
            notifyDataSetChanged();
        }
    }

    /**
     * 移除头部View
     *
     * @param view View
     */
    public void removeHeaderView(View view) {
        int index = headerViews.indexOfValue(view);
        if (index > -1) {
            headerViews.removeAt(index);
            notifyDataSetChanged();
        }
    }

    /**
     * 移除底部View
     *
     * @param view View
     */
    public void removeFooterView(View view) {
        int index = footerViews.indexOfValue(view);
        if (index > -1) {
            footerViews.removeAt(index);
            notifyDataSetChanged();
        }
    }

}

三、GeneralWrapRecyclerView

/**
 * 作者: 叶应是叶
 * 时间: 2017/6/4
 * 描述: 可以带头部View与尾部View的RecyclerView
 */
public class GeneralWrapRecyclerView extends RecyclerView {

    private GeneralWrapRecyclerAdapter mRecyclerAdapter;

    public GeneralWrapRecyclerView(Context context) {
        super(context);
    }

    public GeneralWrapRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GeneralWrapRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setAdapter(GeneralWrapRecyclerAdapter recyclerAdapter) {
        this.mRecyclerAdapter = recyclerAdapter;
        super.setAdapter(recyclerAdapter);
    }

    @Override
    public void setAdapter(Adapter adapter) {
        
    }

    /**
     * 添加头部View
     *
     * @param view View
     */
    public void addHeaderView(View view) {
        if (mRecyclerAdapter != null) {
            mRecyclerAdapter.addHeaderView(view);
        } else {
            throw new RuntimeException("GeneralRecyclerAdapter == null");
        }
    }

    /**
     * 添加底部View
     *
     * @param view View
     */
    public void addFooterView(View view) {
        if (mRecyclerAdapter != null) {
            mRecyclerAdapter.addFooterView(view);
        } else {
            throw new RuntimeException("GeneralRecyclerAdapter == null");
        }
    }

    /**
     * 移除头部View
     *
     * @param view View
     */
    public void removeHeaderView(View view) {
        if (mRecyclerAdapter != null) {
            mRecyclerAdapter.removeHeaderView(view);
        } else {
            throw new RuntimeException("GeneralRecyclerAdapter == null");
        }
    }

    /**
     * 移除底部View
     *
     * @param view View
     */
    public void removeFooterView(View view) {
        if (mRecyclerAdapter != null) {
            mRecyclerAdapter.removeFooterView(view);
        } else {
            throw new RuntimeException("GeneralRecyclerAdapter == null");
        }
    }

}

四、实际使用

这里在为 GeneralWrapRecyclerView 设置 Adapter 时就只能将参数值设置为 GeneralWrapRecyclerAdapter 类的对象了

public class GeneralWrapRecyclerActivity extends AppCompatActivity implements GeneralWrapRecyclerHolder.onClickCommonListener, View.OnClickListener {

    private List<Data> dataList;

    private List<View> headerViewList;

    private List<View> footerViewList;

    private GeneralWrapRecyclerView generalWrapRecyclerView;

    private MyGeneralWrapRecyclerAdapter myGeneralWrapRecyclerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_general_wrap_recycler);
        initData();
        generalWrapRecyclerView = (GeneralWrapRecyclerView) findViewById(R.id.rv_testRecyclerView);
        generalWrapRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        myGeneralWrapRecyclerAdapter = new MyGeneralWrapRecyclerAdapter(this, dataList, R.layout.item, this);
        generalWrapRecyclerView.setAdapter(myGeneralWrapRecyclerAdapter);
        View headerView1 = getLayoutInflater().inflate(R.layout.header_view, generalWrapRecyclerView, false);
        View headerView2 = getLayoutInflater().inflate(R.layout.header_view, generalWrapRecyclerView, false);
        View footerView1 = getLayoutInflater().inflate(R.layout.footer_view, generalWrapRecyclerView, false);
        View footerView2 = getLayoutInflater().inflate(R.layout.footer_view, generalWrapRecyclerView, false);
        generalWrapRecyclerView.addHeaderView(headerView1);
        generalWrapRecyclerView.addHeaderView(headerView2);
        generalWrapRecyclerView.addFooterView(footerView1);
        generalWrapRecyclerView.addFooterView(footerView2);
        headerViewList.add(headerView1);
        headerViewList.add(headerView2);
        footerViewList.add(footerView1);
        footerViewList.add(footerView2);
        findViewById(R.id.btn_addData).setOnClickListener(this);
        findViewById(R.id.btn_deleteData).setOnClickListener(this);
        findViewById(R.id.btn_addHeaderView).setOnClickListener(this);
        findViewById(R.id.btn_deleteHeaderView).setOnClickListener(this);
        findViewById(R.id.btn_addFooterView).setOnClickListener(this);
        findViewById(R.id.btn_deleteFooterView).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_addData:
                Data data = new Data(R.mipmap.ic_launcher, "Hi");
                dataList.add(data);
                myGeneralWrapRecyclerAdapter.notifyDataSetChanged();
                break;
            case R.id.btn_deleteData:
                if (dataList.size() > 0) {
                    dataList.remove(0);
                }
                myGeneralWrapRecyclerAdapter.notifyDataSetChanged();
                break;
            case R.id.btn_addHeaderView:
                View headerView = getLayoutInflater().inflate(R.layout.header_view, generalWrapRecyclerView, false);
                headerViewList.add(headerView);
                generalWrapRecyclerView.addHeaderView(headerView);
                break;
            case R.id.btn_deleteHeaderView:
                if (headerViewList.size() > 0) {
                    generalWrapRecyclerView.removeHeaderView(headerViewList.get(0));
                    headerViewList.remove(0);
                }
                break;
            case R.id.btn_addFooterView:
                View footerView = getLayoutInflater().inflate(R.layout.footer_view, generalWrapRecyclerView, false);
                footerViewList.add(footerView);
                generalWrapRecyclerView.addFooterView(footerView);
                break;
            case R.id.btn_deleteFooterView:
                if (footerViewList.size() > 0) {
                    generalWrapRecyclerView.removeFooterView(footerViewList.get(0));
                    footerViewList.remove(0);
                }
                break;
        }
    }

    private void initData() {
        dataList = new ArrayList<>();
        headerViewList = new ArrayList<>();
        footerViewList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Data data = new Data(R.mipmap.ic_launcher_round, "Hi:" + i);
            dataList.add(data);
        }
    }

    @Override
    public void onClick(int position) {
        Toast.makeText(this, "点击:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClick(int position) {
        Toast.makeText(this, "长按:" + position, Toast.LENGTH_SHORT).show();
    }

}

运行效果:

这里写图片描述

这里提供代码下载:解析RecyclerView(3)——以更简单的方法实现带顶部View和底部View的RecyclerView

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ascii0x03的安全笔记

Python写的嗅探器——Pyside,Scapy

使用Python的Pyside和Scapy写的嗅探器原型,拥有基本框架,但是功能并不十分完善,供参考。 ? 1 import sys 2 import ...

4678
来自专栏Jack的Android之旅

Android NestedScrolling机制

NestedScrolling机制现在在App的作用越来越重要,许多很漂亮的交互都是基于NestedScrolling机制进行完成的。

752
来自专栏Android开发指南

10. 面向holder编程、自动轮询

34612
来自专栏Android知识点总结

3-VII-RecyclerView的item操作

927
来自专栏开发之途

Android 解决 View 的滑动冲突

关于 Android 的 TouchEvent 事件分发机制可以看这里:Java_Android_Learn,本文讲解的是如何去解决 View 之间的滑动冲突

651
来自专栏Android源码框架分析

从Toast显示原理初窥Android窗口管理

Android窗口管理系统是非常大的一块,涉及AMS、InputManagerService、输入法管理等,这么复杂的一个系统,如果直接扎进入分析看源码可能会比...

1224
来自专栏Android开发指南

7.首页、bitmaputils

3448
来自专栏技术小黑屋

Android支持RTL(从右向左)语言

未加入android:supportsRtl=“true” 阿拉伯语(RTL)的示例.

1012
来自专栏技术小黑屋

关于获取当前Activity的一些思考

在Android开发过程中,我们有时候需要获取当前的Activity实例,比如弹出Dialog操作,必须要用到这个。关于如何实现由很多种思路,这其中有的简单,有...

543
来自专栏Android Note

RecycleView的拖动排序

962

扫码关注云+社区