RecyclerView下拉刷新、上拉加载更多以及复杂列表布局的实现

Android实现RecyclerView的下拉刷新和上拉加载更多 需求先上效果图, Material Design风格的下拉刷新和上拉加载更多。 源码地址(欢迎star) https://github.com/studychen/SeeNewsV2如果对于RecyclerView还不熟悉,参见这篇 Android Material Design学习之RecyclerView代替 ListView下拉刷新效果图上拉时候会有一个圆形动画,刷新加载数据。思路使用Goog 阅读详情

介绍:关于recyclerView大家一定不陌生,可以实现listView,gridView,瀑布流的功能,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。

本文主要介绍的内容如下:

  • 对recyclerView的适配器封装一些通用方法
  • 实现recyclerView的下拉刷新以及上拉加载更多(当然也是可以关闭的)
  • 优雅实现recyclerView复杂列表布局(ps:部分数据一列加载,部分数据两列加载等等)

当然,如果你对recyclerView不怎么了解的话,建议先看看鸿洋的博客Android RecyclerView 使用完全解析 体验艺术般的控件,他介绍的非常详细了,只不过在这里我做了一些补充

先看下效果图:

recyclerView

简单的介绍一下效果图,可以看到布局显示中,有的是黄色方块,有的是红色方块,这是两种不同的布局。我让红色方块一整行显示,而黄色方块半行显示。实际上就是根据数据类型的不同,来以特定的方式展示。这里如果需要加一个头部信息(类似联系人列表的那种)都是同理的。
下面开始介绍代码的实现过程:
step1:封装recyclerView的适配器,即RecyclerViewAdapter。实际上就是封装了Adapter基类,实现一些最常用的方法,避免重复造轮子。代码如下:
RecyclerViewAdapter
/**
 * Created by imSunLight on 2016/12/26.
 * 邮箱:1944633835@qq.com
 */
public abstract class RecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    protected Context mContext;
    protected LayoutInflater mInflater;
    private int preSize = 0;
    //判断需不需要上拉加载更多
    private boolean isLoadingMore = false;

    //点击事件回调监听
    private RecyclerViewCallback<T> mRecyclerViewCallback;


    protected List<T> mData = new ArrayList<>();

    public RecyclerViewAdapter(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
    }


    @Override
    public int getItemCount() {

        //根据是否下拉加载更多来决定数据的大小
        if (isLoadingMore()) {
            return mData.size() + 1;
        }
        return mData.size();
    }

    public RecyclerViewCallback<T> getRecyclerViewCallback() {
        return mRecyclerViewCallback;
    }

    public void setRecyclerViewCallback(RecyclerViewCallback recyclerViewCallback) {
        this.mRecyclerViewCallback = recyclerViewCallback;
    }

    public boolean isLoadingMore() {
        return isLoadingMore;
    }


    public void setLoadingMore(boolean loadingMore) {
        isLoadingMore = loadingMore;
    }

    /**
     * 设置数据
     *
     * @param list
     */
    public void setData(List<T> list) {
        mData.addAll(list);
        preSize = mData.size();
    }

    /**
     * 设置数据源
     *
     * @param data
     */
    public void setData(T[] data) {
        if (data != null && data.length > 0) {
            setData(Arrays.asList(data));
        }
    }

    /**
     * 添加数据
     *
     * @param data
     */
    public void addData(List<T> data) {
        int preSize = this.mData.size();
        if (data != null && data.size() > 0) {
            if (this.mData == null) {
                this.mData = new ArrayList<T>();
            }
            this.mData.addAll(data);
            notifyItemRangeInserted(preSize, this.mData.size());
        }
        notifyDataSetChanged();
    }

    /**
     * 添加数据
     *
     * @param data
     */
    public void addData(T[] data) {
        addData(Arrays.asList(data));
    }

    /**
     * 删除元素
     *
     * @param element
     */
    public void removeElement(T element) {
        if (mData.contains(element)) {
            int position = mData.indexOf(element);
            mData.remove(element);
            notifyItemRemoved(position);
            notifyItemChanged(position);
        }
    }

    /**
     * 删除元素
     *
     * @param position
     */
    public void removeElement(int position) {
        if (mData != null && mData.size() > position) {
            mData.remove(position);
            notifyItemRemoved(position);
            notifyItemChanged(position);
        }
    }

    /**
     * 删除元素
     *
     * @param elements
     */
    public void removeElements(List<T> elements) {
        if (mData != null && elements != null && elements.size() > 0
                && mData.size() >= elements.size()) {

            for (T element : elements) {
                if (mData.contains(element)) {
                    mData.remove(element);
                }
            }

            notifyDataSetChanged();
        }
    }

    /**
     * 删除元素
     *
     * @param elements
     */
    public void removeElements(T[] elements) {
        if (elements != null && elements.length > 0) {
            removeElements(Arrays.asList(elements));
        }
    }

    /**
     * 更新元素
     *
     * @param element
     * @param position
     */
    public void updateElement(T element, int position) {
        if (position >= 0 && mData.size() > position) {
            mData.remove(position);
            mData.add(position, element);
            notifyItemChanged(position);
        }
    }

    /**
     * 添加元素
     *
     * @param element
     */
    public void addElement(T element) {
        if (element != null) {
            if (this.mData == null) {
                this.mData = new ArrayList<T>();
            }
            mData.add(element);
            notifyItemInserted(this.mData.size());
        }
    }

    public void addElement(int position, T element) {
        if (element != null) {
            if (this.mData == null) {
                this.mData = new ArrayList<T>();
            }
            mData.add(position, element);
            notifyItemInserted(position);
        }
    }

    /**
     * 清除数据源
     */
    public void clearData() {
        if (this.mData != null) {
            this.mData.clear();
            notifyDataSetChanged();
        }
    }


    /**
     * 设置控件可见
     *
     * @param view
     */
    protected void setVisible(View view) {
        view.setVisibility(View.VISIBLE);
    }

    /**
     * 设置控件不可见
     *
     * @param view
     */
    protected void setGone(View view) {
        view.setVisibility(View.GONE);
    }

    /**
     * 设置控件不可见
     *
     * @param view
     */
    protected void setInvisible(View view) {
        view.setVisibility(View.INVISIBLE);
    }

    /**
     * 设置控件可用
     *
     * @param view
     */
    protected void setEnable(View view) {
        view.setEnabled(true);
    }

    /**
     * 设置控件不可用
     *
     * @param view
     */
    protected void setDisable(View view) {
        view.setEnabled(false);
    }

    /**
     * 获取图片资源
     *
     * @param resId
     * @return
     */
    protected Drawable getDrawable(int resId) {
        return mContext.getResources().getDrawable(resId);
    }

    /**
     * 获取字符串资源
     *
     * @param resId
     * @return
     */
    protected String getString(int resId) {
        return mContext.getResources().getString(resId);
    }

    /**
     * 获取颜色资源
     *
     * @param resId
     * @return
     */
    protected int getColor(int resId) {
        return mContext.getResources().getColor(resId);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public List<T> getDataSource() {
        return mData;
    }

}

根据适配器的异同来封装总结了通用型的RecyclerViewAdapter(一般来说不同点就是传入的javaBean类吧,所以用泛型类T表示),对此我对适配器做了增删改查方法等一系列封装。当然这里还可以做更多扩展,其实就是Adapter的基类,暂时就封装这么多,以后在慢慢增加。
有个地方需要说明一下:

    @Override
     public int getItemCount() {
         //根据是否下拉加载更多来决定数据的大小
         if (isLoadingMore()) {
             return mData.size() + 1;
         }
         return mData.size();
     }

在getItemCount()这个方法,要根据是否要添加下拉加载这个模块,如果添加下拉加载更多,那么应该返回的数量大小应该是在mData.size() + 1吧(因为如果要上拉加载更多,那么肯定会多一个item数量的,所以要加1)。否则的话,就是返回mData.size() 就可以了。

这只是个抽象类,具体到我们这个情景就是写个适配器来继承RecyclerViewAdapter了,在个demo里呢,我写了一个SimpleAdapter来继承RecyclerViewAdapter,这里的数据类型是DataModel,当然这个类应该是根据实际业务需求来确定,一般是根据从服务端获取到的数据来确定
step2:实现DataModel
/**
 * Created by imSunLight on 2016/12/26.
 * 邮箱:1944633835@qq.com
 */
public class DataModel {

    public  static   final  int  TYPE_ONE=1;
    public static    final  int TYPE_TWO=2;

    private  int dataType;

    private  String title;
    private String descreption;

    public DataModel(String title, String descreption, int dataType) {
        this.title = title;
        this.descreption = descreption;
        this.dataType = dataType;
    }

    public int getDataType() {
        return dataType;
    }

    public void setDataType(int dataType) {
        this.dataType = dataType;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescreption() {
        return descreption;
    }

    public void setDescreption(String descreption) {
        this.descreption = descreption;
    }

    @Override
    public String toString() {
        return "DataModel{" +
                "TYPE_ONE=" + TYPE_ONE +
                ", TYPE_TWO=" + TYPE_TWO +
                ", dataType=" + dataType +
                ", title='" + title + '\'' +
                ", descreption='" + descreption + '\'' +
                '}';
    }
}

说明:正如一开始的效果图所示,有黄色方块跟红色方块,分别代表了两种数据类型,所以在DataModel这个类中创建了一个int dataType属性。

step3:实现SimpleAdapter
/**
 * Created by gechuanguang on 2016/12/28.
 * 邮箱:1944633835@qq.com
 */

public class SimpleAdapter extends RecyclerViewAdapter<DataModel> {

    public static final int TYPE_FOOTER = 1003;

    public static  final  int STATE_LOADINGMORE=100120;
    public static  final  int STATE_NOMOREDATA=100121;

    private int Loadingstate=STATE_LOADINGMORE;

    public SimpleAdapter(Context context) {
        super(context);
        Loadingmore(true);
    }

    @Override
    public int getItemViewType(int position) {
        super.getItemViewType(position);
        if (position == mData.size()) {
            //说明加载到最底部了  -->显示底部布局
            return TYPE_FOOTER;
        }
        //不是底部,那么必然是内容区域布局了
        DataModel dataModel = mData.get(position);
        int dataType = dataModel.getDataType();
        if (dataType == DataModel.TYPE_ONE) {
            return DataModel.TYPE_ONE;
        }
        return DataModel.TYPE_TWO;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = null;
        ItemHolder itemHolder = null;
        switch (viewType) {
            case DataModel.TYPE_ONE:
                view = mInflater.inflate(R.layout.item_typeone, parent, false);
                itemHolder = new ItemHolder(view,DataModel.TYPE_ONE);
                break;
            case DataModel.TYPE_TWO:
                view = mInflater.inflate(R.layout.item_typetwo, parent, false);
                itemHolder = new ItemHolder(view,DataModel.TYPE_TWO);
                break;
            case TYPE_FOOTER:
                view = mInflater.inflate(R.layout.footer, parent, false);
                itemHolder = new ItemHolder(view,TYPE_FOOTER);
                break;
        }
        return itemHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

        //首先要判断是不是底部布局
        final int itemViewType = getItemViewType(position);
        if (itemViewType == TYPE_FOOTER) {
            String tips = "";
            if (Loadingstate==STATE_LOADINGMORE) {
                tips = "加载更多...";
            } else if(Loadingstate==STATE_NOMOREDATA){
                tips = "没有更多数据!!!";
            }
            ((ItemHolder) holder).showTips.setText(tips);
            return;
        }

        //内容区域布局
        final DataModel dataMode = mData.get(position);

        if (itemViewType == DataModel.TYPE_ONE) {
            ((ItemHolder) holder).title.setText(dataMode.getTitle() );
            ((ItemHolder) holder).description.setText("typeOne:\n" + dataMode.getDescreption());
            ((ItemHolder) holder).mImageView.setBackgroundColor(Color.parseColor("#f3f33f"));
        } else {
            ((ItemHolder) holder).title.setText(dataMode.getTitle() );
            ((ItemHolder) holder).description.setText("typetwo:\n" + dataMode.getDescreption());
            ((ItemHolder) holder).mImageView.setBackgroundColor(Color.parseColor("#ff22ff"));
            ((ItemHolder) holder).mImageView2.setBackgroundColor(Color.parseColor("#ff22ff"));
        }
        //点击事件
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (getRecyclerViewCallback() != null) {
                    getRecyclerViewCallback().OnItemClickListener(position, dataMode, itemViewType);
                }
            }
        });
        //长按事件
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (getRecyclerViewCallback() != null) {
                    getRecyclerViewCallback().OnItemLongClickListener(position, dataMode, itemViewType);
                }
                return true;
            }
        });
    }

    /**
     *  上拉加载开关
     * @param isLoadingmore  true:上拉加载, false:关闭加载(并且隐藏了底部布局)
     */
    public void Loadingmore(boolean isLoadingmore) {
        if(isLoadingmore){
            setLoadingMore(isLoadingmore);
            Loadingstate=STATE_LOADINGMORE;
            this.notifyDataSetChanged();
        }

    }

    /**
     * 没有更多数据加载
     */
    public void LoadIngNoMoreData(){

        Loadingmore(true);
        this.Loadingstate=STATE_NOMOREDATA;
        this.notifyDataSetChanged();
    }
}
下面开始介绍SimpleAdapter这个类一些主要方法:
  • 首先是getItemViewType()这个方法
   @Override
    public int getItemViewType(int position) {
        super.getItemViewType(position);
        if (position == mData.size()) {
            //说明加载到最底部了  -->显示底部布局
            return TYPE_FOOTER;
        }
        //不是底部,那么必然是内容区域布局了
        DataModel dataModel = mData.get(position);
        int dataType = dataModel.getDataType();
        if (dataType == DataModel.TYPE_ONE) {
            return DataModel.TYPE_ONE;
        }
        return DataModel.TYPE_TWO;
    }

根据position判断是不是滑到最后一个元素了,如果是的,那么就是显示底部布局,即返回一个int类型的标志;否则的话,就要根据position来判断要加载的数据类型,然后返回不同的int类型标志。

  • 其次是onCreateViewHolder()方法,即创建一个ViewHolder
 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = null;
        ItemHolder itemHolder = null;
        switch (viewType) {
            case DataModel.TYPE_ONE:
                view = mInflater.inflate(R.layout.item_typeone, parent, false);
                itemHolder = new ItemHolder(view,DataModel.TYPE_ONE);
                break;
            case DataModel.TYPE_TWO:
                view = mInflater.inflate(R.layout.item_typetwo, parent, false);
                itemHolder = new ItemHolder(view,DataModel.TYPE_TWO);
                break;
            case TYPE_FOOTER:
                view = mInflater.inflate(R.layout.footer, parent, false);
                itemHolder = new ItemHolder(view,TYPE_FOOTER);
                break;
        }
        return itemHolder;
    }

那么这个ItemHolder哪里来的呢,其实要我是把这一块抽离出来了(不然怎么说是优雅实现recyclerView复杂列表布局呢)。我是先创建了一个BaseViewHolder,然后ItemHolder来继承BaseViewHolder

BaseViewHoler实现如下:
/**
 * Created by gechuanguang on 2016/12/26.
 * 邮箱:1944633835@qq.com
 */

public abstract  class BaseViewHolder extends RecyclerView.ViewHolder {

    public BaseViewHolder(View itemView,int viewType) {
        super(itemView);

        if(viewType== DataModel.TYPE_ONE){
            initTypeOneLayout(itemView);
        }
        if(viewType==DataModel.TYPE_TWO){
            initTypeTwoLayout(itemView);
        }
        if(viewType== SimpleAdapter.TYPE_FOOTER){
            initTypeFooterlayout(itemView);
        }

    }

    abstract void initTypeOneLayout(View itemView);
    abstract void initTypeTwoLayout(View itemView);
    abstract void initTypeFooterlayout(View itemView);

}

在构造方法中传一个viewType,来确定我们要初始化哪些控件。这是一个抽象类,我们需要一个具体的ItemHolder来继承它

ItemHolder实现如下:
public class ItemHolder extends BaseViewHolder {
    public TextView title;
    public TextView description;
    public ImageView mImageView;
    public ImageView mImageView2;
    //底部布局
    public  TextView showTips;

    public ItemHolder(View itemView,int viewType) {
        super(itemView,viewType);
    }

    @Override
    void initTypeOneLayout(View itemView) {
        title = (TextView) itemView.findViewById(R.id.title);
        description = (TextView) itemView.findViewById(R.id.des);
        mImageView = (ImageView) itemView.findViewById(R.id.img);
    }

    @Override
    void initTypeTwoLayout(View itemView) {
        title = (TextView) itemView.findViewById(R.id.title);
        description = (TextView) itemView.findViewById(R.id.des);
        mImageView = (ImageView) itemView.findViewById(R.id.img);
        mImageView2 = (ImageView) itemView.findViewById(R.id.img2);
    }

    @Override
    void initTypeFooterlayout(View view) {
        showTips= (TextView) view.findViewById(R.id.tv_showTips);
    }
}
ok,ItemHolder已经实现了,回归到上面onCreateViewHolder()这个方法,根据viewType来创建ItemHolder。
  • 再其次就是onBindViewHolder(RecyclerView.ViewHolder holder, final int position)这个方法,在这里就要填充对应的数据了
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

        //首先要判断是不是底部布局
        final int itemViewType = getItemViewType(position);
        if (itemViewType == TYPE_FOOTER) {
            String tips = "";
            if (Loadingstate==STATE_LOADINGMORE) {
                tips = "加载更多...";
            } else if(Loadingstate==STATE_NOMOREDATA){
                tips = "没有更多数据!!!";
            }
            ((ItemHolder) holder).showTips.setText(tips);
            return;
        }

        //内容区域布局
        final DataModel dataMode = mData.get(position);

        if (itemViewType == DataModel.TYPE_ONE) {
            ((ItemHolder) holder).title.setText(dataMode.getTitle() );
            ((ItemHolder) holder).description.setText("typeOne:\n" + dataMode.getDescreption());
            ((ItemHolder) holder).mImageView.setBackgroundColor(Color.parseColor("#f3f33f"));
        } else {
            ((ItemHolder) holder).title.setText(dataMode.getTitle() );
            ((ItemHolder) holder).description.setText("typetwo:\n" + dataMode.getDescreption());
            ((ItemHolder) holder).mImageView.setBackgroundColor(Color.parseColor("#ff22ff"));
            ((ItemHolder) holder).mImageView2.setBackgroundColor(Color.parseColor("#ff22ff"));
        }
        //点击事件
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (getRecyclerViewCallback() != null) {
                    getRecyclerViewCallback().OnItemClickListener(position, dataMode, itemViewType);
                }
            }
        });
        //长按事件
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (getRecyclerViewCallback() != null) {
                    getRecyclerViewCallback().OnItemLongClickListener(position, dataMode, itemViewType);
                }
                return true;

        });
    }

简单介绍一下这个方法做了哪些逻辑的处理,首先判断我们加载的是不是底部布局,并且根据Loadingstate来决定底部加载框的提示内容(加载更多or没有更多数据);如果是内容区域的话,就填充对应的数据。其次是点击事件,我们知道RecyclerView是没有给我们提供点击事件的,所以这个得自己实现,方法也很简单,就是定义一个接口,通过接口回调的方式得到对应的position已经其他数据。
定义的回调接口如下:

/**
 * Created by gechuanguang on 2016/12/28.
 * 邮箱:1944633835@qq.com
 */

public interface RecyclerViewCallback<T> {

    /**
     * recyclerView点击事件回调
     *
     * @param position  位置
     * @param dataModel 对应的实体类
     * @param flag      布局的类型
     */
    void OnItemClickListener(int position, T dataModel, int flag);

    /**
     * recyclerView 长按事件回调
     *
     * @param position  位置
     * @param dataModel 对应的实体类
     * @param flag      布局的类型
     */
    void OnItemLongClickListener(int position, T dataModel, int flag);
}
  • 还有最后一个就是上拉加载更多的设置
    /**
     *  上拉加载开关
     * @param isLoadingmore  true:上拉加载, false:关闭加载(并且隐藏了底部布局)
     */
    public void Loadingmore(boolean isLoadingmore) {
        if(isLoadingmore){
            setLoadingMore(isLoadingmore);
            Loadingstate=STATE_LOADINGMORE;
            this.notifyDataSetChanged();
        }

    }

    /**
     * 没有更多数据加载
     */
    public void LoadIngNoMoreData(){

        Loadingmore(true);
        this.Loadingstate=STATE_NOMOREDATA;
        this.notifyDataSetChanged();
    }
}

因为有时候我们是不需要有上拉加载更多的,为了灵活性,得在这里设置是否开启的加载的开关,即Loadingmore(boolean isLoadingmore)这个方法,通过传入trueorfalse,来决定底部布局是否显示。如果开启上拉加载更多并且没有更多数据了,这个也该有个提示吧,so,定义了LoadIngNoMoreData()这个方法,来提示没有更多数据要加载了

到此为止,核心的东西差不多都介绍完了,接下来看看在Activity或者Fragment中怎么去使用RecyclerView吧

MainActivity的实现:


public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {

    private RecyclerView mRecyclerView;
    private SimpleAdapter mAdapter;
    private Context mContext = this;
    private List<DataModel> mList = new ArrayList<>();
    private GridLayoutManager mLayoutManager;
    private SwipeRefreshLayout mSwipeRefreshLayout;


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

        initView();

        //设置刷新回调
        mSwipeRefreshLayout.setOnRefreshListener(this);
        mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimaryDark,
                R.color.colorAccent, R.color.colorPrimary, R.color.colorAccent);

        mRecyclerView.addItemDecoration(new DividerGridItemDecoration(mContext));
        mAdapter = new SimpleAdapter(mContext);
        mLayoutManager = new GridLayoutManager(mContext, 2);
        //动态控制显示列数
        mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                int itemViewType = mAdapter.getItemViewType(position);
                if (itemViewType == DataModel.TYPE_ONE) {
                    return 1;
                } else {
                    return 2;
                }
            }
        });
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);

        initData();
        initListener();
        mRecyclerView.addOnScrollListener(mOnScrollListener);


//        mSwipeRefreshLayout.setEnabled(false);

    }

    RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
        int lastVisibleItemPosition = 0;

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition + 1 == mAdapter.getItemCount()) {
                /**--此处要做的是从后台请求数据,判断是否有更多数据要加载--**/
                boolean hasmoreData = true;//实际情况——>根据后台返回的json的某个字段判断
                if (hasmoreData) {
                    //开启下拉加载
                    mAdapter.Loadingmore(true);
                    //此处实际开发中,应该是从后台请求下一页的数据
                    //模仿 添加一些测试数据

                    List<DataModel> moreData = new ArrayList<>();
                    for (int i = 0; i < 5; i++) {
                        moreData.add(new DataModel("下拉刷新:" + i, "des" + i, DataModel.TYPE_TWO));
                    }
                    mAdapter.addData(moreData);
                    return;
                }

                //没有更多数据---> 此处根据需求来做,当没有更多数据的时候是隐藏底部布局,还是显示底部布局,并且提示没更多数据
                mAdapter.LoadIngNoMoreData();
            }
        }

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

    private void initListener() {
        //点击事件
        mAdapter.setRecyclerViewCallback(new RecyclerViewCallback() {
            @Override
            public void OnItemClickListener(int position, Object dataModel, int flag) {
                Toast.makeText(mContext, "点击了:" + position, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void OnItemLongClickListener(int position, Object dataModel, int flag) {
                Toast.makeText(mContext, "长按了" + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void initView() {
        mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeLayout);
        mRecyclerView = (RecyclerView) findViewById(R.id.rl);
    }

    private void initData() {
        //模拟一些list数据
        for (int i = 0; i < 15; i++) {
            DataModel model;
            double random = Math.random();
            if (random < 0.5) {
                model = new DataModel("title" + i, "description" + i, DataModel.TYPE_ONE);
            } else {
                model = new DataModel("title" + i, "description" + i, DataModel.TYPE_TWO);
            }
            mList.add(model);
        }
        mAdapter.setData(mList);
    }


    //下拉刷新
    @Override
    public void onRefresh() {
        //重新刷新 加载  (既然是刷新,就应该把之前的数据集都清除掉,再重新请求)
        mAdapter.clearData();

        //伪造一些数据,实际情况应该在此处从后台请求最新的20条数据(条数按实际情况确定)
        List<DataModel> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add(new DataModel("title:刷新的数据" + i, "des:刷新的数据" + i, DataModel.TYPE_TWO));
        }
        mAdapter.addData(list);
        mSwipeRefreshLayout.setRefreshing(false);
    }
}

在MainActivity中实现了下拉刷新,上拉加载更多,以及数据的初始化操作。下拉刷新很简单,只要在RecyclerView外面包裹一个SwipeRefreshLayout就可以了。

activity_main.xml实现如下;
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipeLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

   <android.support.v7.widget.RecyclerView
       android:id="@+id/rl"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>
接下来介绍一下MainActivity里面的一些实现过程,主要以下几点:
  • 下拉刷新

    要实现下拉刷新,就要给SwipeRefreshLayou设置下拉监听,即mSwipeRefreshLayout.setOnRefreshListener(this);紧接着就会回调onRefresh()这个方法给我们,执行了这个方法就说明我们刷新了,我想说的是,一旦刷新我们就要把以前的数据集都清除调,然后再传给适配器吧,即首先要mAdapter.clearData();然后在add新的数据。如果不想添加下拉刷新了,就不想改变xml布局,那么也可以mSwipeRefreshLayout.setEnabled(false);不让它刷新

  • 填充复杂列表布局

    如何动态的设置,让某些数据一行显示而让另一些数据半行显示呢,其实就是mLayoutManager.setSpanSizeLookup()实现。

     mLayoutManager = new GridLayoutManager(mContext, 2);
           //动态控制显示列数
           mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
               @Override
               public int getSpanSize(int position) {
                   int itemViewType = mAdapter.getItemViewType(position);
                   if (itemViewType == DataModel.TYPE_ONE) {
                       return 1;
                   } else {
                       return 2;
                   }
               }
           });
    注意:当retrun 1的时候,就是每个item占据1/2行,当return 2的时候,就是每个item占据2/2行。
  • 上拉加载更多

    主要给RecyclerView设置mRecyclerView.addOnScrollListener(mOnScrollListener);

mOnScrollListener对象如下:
  RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
        int lastVisibleItemPosition = 0;

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition + 1 == mAdapter.getItemCount()) {
                /**--此处要做的是从后台请求数据,判断是否有更多数据要加载--**/
                boolean hasmoreData = true;//实际情况——>根据后台返回的json的某个字段判断
                if (hasmoreData) {
                    //开启下拉加载
                    mAdapter.Loadingmore(true);
                    //此处实际开发中,应该是从后台请求下一页的数据
                    //模仿 添加一些测试数据

                    List<DataModel> moreData = new ArrayList<>();
                    for (int i = 0; i < 5; i++) {
                        moreData.add(new DataModel("下拉刷新:" + i, "des" + i, DataModel.TYPE_TWO));
                    }
                    mAdapter.addData(moreData);
                    return;
                }

                //没有更多数据---> 此处根据需求来做,当没有更多数据的时候是隐藏底部布局,还是显示底部布局,并且提示没更多数据
                mAdapter.LoadIngNoMoreData();
            }
        }

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

其实代码里注释很清楚了,根据后台返回的数据来判断是否有更多数据,如果有那么就要开启下拉加载更多开关,如果没有更多数据了,就调用mAdapter.LoadIngNoMoreData();

好了,关于RecyclerView的下拉刷新,上拉加载更多,以及recyclerView的复杂列表的实现方式已经介绍完了。虽然这只是个demo,但是具体到一个项目中,也能灵活改变的,原理不变。
如果还不是很清楚的,请参考源码RecyclerView-master
【Android从零撸美团四】美团首页布局解析及实现 - Banner+自定义View+SmartRefreshLayout下拉刷新上拉加载更多 这是【从零撸美团】系列文章第四篇。 项目地址:https://github.com/cachecats/LikeMeiTuan 仿美团开源项目整体架构和首页其实早就完成了,前段时间家里各种事情搞得心力交瘁,停更了一段时间。甚至一度动摇继续这个项目的决心,因为最近在学前端,在技术的深度和广度之间一直纠结摇摆不定。一个声音是继续完成这个项目,把安卓玩的更深入一些;另一个声音是赶紧学前端吧,抓紧拓展技... 阅读详情

相关推荐

RecyclerView实现多种item布局

在项目中列表是基本都会用到的,然而在显示列表时,我们需要的数据可能需要不止一种item显示,对于复杂的数据就需要多种item,以不同的样式显示出来,这样效果是很棒的,我们先看一下效果 我们可以看到,这个RecyclerView中有多种item显示出来,那么具体怎么实现呢,其实在RecyclerView中,我们可以重写方法getItemViewType(),这个方法会传进一个参数posit

谷哥的小弟 3万+

Android使用RecyclerView实现上拉加载更多,下拉刷新,分组显示

项目地址:点击打开链接(https://github.com/MrGaoGang/luckly_recyclerview) 使用RecyclerView封装headerview,footerView,并实现上拉加载更多,下拉刷新,分组功能(添加上拉加载和下拉刷新设置背景) 界面可能有点丑,但是是为了展示所有效果,请大家见谅。(所有东西都是可自己设置的哦) 效果图: 如何...

mrgao的博客 7524

自定义RecyclerView添加下拉刷新和上拉加载功能

自定义RecyclerView中遇见的问题通用实现起来复杂,效率高则简单,但是如何实现高效和简单呢,就是把功能开源成库工程,这样的话既提高了效率,又提高了通用性。而且不要重复造轮子,要学会在别人的基础上,取其精华,弄清原理,根据自己的业务需求创造出属于自己的轮子。第一步,建立安卓库工程在Android Studio 中的菜单栏找到,File->New->New Module 然后填写一些基本资料

琦琦进阶之路博客 1229

Android下拉刷新和上拉加载更多

Android下拉刷新和上拉加载更多 下拉刷新 通过android系统提供的组件:SwipeRefreshLayout 一、基本使用 1 xml中 添加 SwipeRefreshLayout 组件 该组件包含着要操作下拉刷新的控件 如ListView RecyclerView 等 注意这里的SwipeRefreshLayout组件的子布局只能有一个 &lt;androi...

5ingwings(SiHao)的博客 2万+

RecyclerView高级使用详解(多套布局,加载更多,监听)

原文/番茄And鸡蛋(简书作者) 原文链接:http://www.jianshu.com/p/7385e2bbb365 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。效果图实现原理1.多套布局利用RecyclerView的特性==》必须实现的ViewHolder类,在定义RecyclerView.Adapter时,在内部实现多个ViewHolder的类,根据不同需求的布局,获取到

snake_son的博客 1079

Recylerview 加载更多功能实现(分页加载)

Recyclerview上拉加载更多功能

Geek的博客 2万+

Android RecyclerView下拉刷新 & 上拉加载更多

GitHub传送门1.写在前面本文主要实现的是上拉加载更多功能,下拉刷新使用的是Google官方的SwipeRefreshLayout控件,因为在实现这个功能的时候走了不少弯路,所以在此记录下来分享给大家,先看下效果图:2.实现上拉加载实际上就是给RecyclerView增加一个FooterView,然后通过判断是否滑动到了最后一条Item,来控制FooterView的显示和隐藏,接下来我们来看下如

YangLe 3885

自定义RecyclerView实现下拉刷新与上拉加载更多功能实战

虽然默认提供了 Material Design 风格的刷新动画,但在品牌化应用或深色主题中,原生样式可能显得突兀。为此,系统提供了丰富的 API 支持颜色、大小、甚至自定义 ProgressDrawable 的修改。同时,添加文字提示能显著提升可用性,尤其是在弱网环境下。init();textView.setText("下拉刷新...");// 添加到布局@Override在现代移动应用中,列表数据的无限滚动已成为标准交互模式之一。

weixin_42527665的博客 1165

RecyclerView三种布局下的上拉加载 下拉刷新

源码地址recyclerview已经代替了listview,它可以实现线性、网络、瀑布流三种样式的局部,很方便使用。但是它没有listview可以添加头部和底部的方法,那上拉加载、下拉刷新怎么实现呢,还好它可以通过viewType来选定在某个特定位置显示对应的布局。我这里指定了四种类型,header、item、footer、endView(没有更多显示的布局)。如何方便的让getItemViewTy

huidawoxiaozi的博客 4007

BaseRecyclerViewAdapterHelper3实战:高效实现RecyclerView下拉刷新与上拉加载

BaseRecyclerViewAdapterHelper(简称 BRVH)是一个专为设计的高效开源适配器库,由第三方开发者团队维护并持续优化。它通过封装常见的列表开发模式,显著简化了Adapter的编写流程。虽然默认的圆形进度条简洁明了,但在品牌化要求较高的应用中往往需要定制化刷新头。可通过继承View或使用(来自 SmartRefreshLayout)实现高级动画。但若坚持使用原生组件,仍可通过扩展实现部分自定义:= nullinit {其中。

weixin_35748962的博客 1039

Android开发实战:自定义ListView实现下拉刷新与上拉加载完整详解

在移动应用开发中,列表控件是用户界面中最常见的组件之一。Android平台提供了ListView和两种核心的列表展示控件。ListView作为早期的标准控件,具备基本的数据绑定和条目复用能力,适用于简单场景。然而随着UI复杂度提升,其扩展性和性能瓶颈逐渐显现。// 典型的BaseAdapter + ViewHolder模式示例@Override} else {由此应运而生,通过将布局管理()、视图持有者(ViewHolder)与适配器(Adapter。

weixin_35750747的博客 1041

Android RecyclerView使用简述

  RecyclerView是Android中非常受欢迎的控件,RecyclerView是官方在Android5.0之后新添加的控件,推出用来替代传统的ListView和GridView列表控件,所以如果你还在使用ListView的话可以替换为RecyclerView了。文章的功能可以先运行看看效果,扫码下载APK   对于RecyclerView的使用根据实际项目进行说明,一些功能可能是你现在正在做的,对你有帮助也说不定。  创建一个名为RecyclerViewDemo的Android项目。注意Andro

晨曦 2万+

【数据挖掘】遗传算法原理浅析:什么是遗传算法?遗传算法的工作原理是什么?

遗传算法(Genetic Algorithm,GA),是进化算法的框架和核心。 各种进化算法有什么异同? - 及时物语的回答 - 知乎 https://www.zhihu.com/question/39152390/answer/901070749

纸羊同学的博客 4280

基于Recyclerview实现复杂布局+SwipeRefreshLayout实现下拉刷新和上拉加载

本文主要介绍上拉加载和下拉刷新,Recyclerview实现复杂布局上篇文章已介绍:http://blog.youkuaiyun.com/qq_34501274/article/details/71194958废话不多说直接进入正文:准备工作都不说了,自己先写好列表,本文主要讲解上拉加载和下拉刷新!效果图 先看布局:<?xml version="1.0" encoding="utf-8"?> <android.

qq_34501274的博客 784

【文本分析学习】Anaconda+pytorch虚拟环境下的pycharm文本分析(含分词、词性标注、词形归一化、停用词、文本情感分析、词频、文本相似度,附数据代码) NLTK

文本分析学习之 Anadconda+pytorch虚拟环境下的pycharm文本分析(含分词、词性标注、词形归一化、停用词、文本情感分析、词频、文本相似度,附数据代码)

littlexmj的博客 2333

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头,才能利用专业设备,里面找到,镜像,把内容数据恢复出来。 1.说一下经历,借用的别人移动硬盘,去帮别人维修,拷数据出来,别人的文件,数据比较多,且复杂,而且是在PE系统,接口什么的是2.0,没有很好的思考就全选复制拷贝了; 2,拷贝的过程,极其慢,文件多,且文件有损坏,移动硬盘一直处于通电的状态,大概从下午,两点多开始100多G的数据,一直卡住,到,晚上7 。8点拷完还是没拷完。打开的时候就已经,就一直

weixin_43766920的博客 4303

RecyclerView下拉刷新上拉加载

下拉刷新 思路:一切复杂的事情都是先从简单入手,分为以下步骤: 1. 我们可以把头部的下拉刷新布局当做列表的一个item,就是头部item,它的布局肯定是跟我们内容不一样的,这个好办,RecyclerView.Adapter类提供了一个getItemViewType(int position)方法,这个方法可以让我们轻松实现一个列表可以有不同布局。

T通天T的博客 435

Qt C++:跨平台开发利器

摘要: Qt C++是基于C++的跨平台开发框架,通过元对象系统、信号槽机制及模块化架构实现"一次编写,多端部署"。核心特性包括: 跨平台能力:覆盖桌面(Windows/macOS/Linux)、移动端(iOS/Android)、嵌入式及WebAssembly; 信号与槽:替代传统回调,实现对象间高效通信; UI组件库:200+预制组件支持QSS样式定制; 工具链:Qt Creator集成开发环境提供可视化设计、调试及多平台编译。 广泛应用于工业控制、车载系统、医疗设备及跨平台应用开发,

2202_75382767的博客 1683

SwipeRefreshLayout和RecyclerView实现下拉刷新和加载更多(当总数据不够一页,也进行了处理)

前言:利用SwipeRefreshLayout和RecyclerView来实现下拉刷新和加载更多,有很多的例子,但普遍都存在一个问题,当总的数据不够一屏时,FooterView也显示了,如果其实就隐藏FooterView,加载更多时FooterView又不显示了,捣鼓了一阵子后,总算完美的解决了,所以记录一下,同时为了方便使用,进行了一些简单的封装。直接进入主题,关于SwipeRefreshLay...

YinRH2014的专栏 4418

TypeError: XGBClassifier.fit() got an unexpected keyword argument ‘early_stopping_rounds‘ 解决方案

本文主要介绍了TypeError: XGBClassifier.fit() got an unexpected keyword argument 'early_stopping_rounds’解决方案,希望能对使用XGBoost的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 1万+

《时序分析、时序约束和时序收敛》专栏的说明与导航

《时序分析、时序约束和时序收敛》专栏的说明与导航(建议阅读)。

孤独的单刀 14万+

Android:RecyclerView 的使用,有这一篇就够了

谨以文章记录学习历程,如有错误还请指明。 RecyclerView 简介 首先,可以理解 RecyclerView 是 ListView 的升级版,更加灵活,同时由于封装了 ListView 的部分实现,导致其使用更简单,结构更清晰。 从名字 RecyclerView 来看,它直接提供了回收复用的功能,虽然 ListView 我们也可以自己实现 ViewHolder 以及 co...

whdAlive 's blog 2万+

基于Jeecgboot3.9.0的vue3版本前后端分离的flowable流程管理平台

基于Jeecgboot3.9.0的vue3版本前后端分离的flowable流程管理平台

宁波阿成的博客 902

RecyclerView#Adapter支持无数据布局、错误布局和列表尾部的”没有更多了“布局

RecyclerView#Adapter支持无数据布局、错误布局和列表尾部的”没有更多了“布局 实际开发中,UI小姐姐都会提供通用的无数据页面、错误提示页面。 针对常见的支持下拉刷新和上拉加载更多的列表页面,将他们的通用逻辑抽取出来,这样我们在开发过程中就只需要关注具体的业务逻辑了,无需每次通过cv来完善无数据页面、错误提示页面的逻辑了。 业务场景梳理 不支持分页 列表页根据业务场景中的是否需要分页来进行区分,不支持分页的逻辑简单。具体如下图所示: 不分页的列表页,请求数据后就三种结果:有数据、无数据、错

tinyVampire的博客 1551

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

上一篇: Android设计模式之Buidler模式
下一篇: Android 调用系统相机,拍照,并上传图片所注意的一些问题
imSunLight
博客等级 码龄11年 24粉丝 19原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值