RecyclerView控件从前年开始发布,google发布此控件就是为了代替过去ListView的列表显示,从回收的效率到 复用到视图与数据容器的设计都比以往要好;

在使用的过程发现,官方有SwipeRefreshLayout支持RecyclerView的刷新,但在加载更多的情况没有接口调用,于是就想DIY一个自定义RecyclerView扩展支持上拉加载更多。

DIY

FylderRecyclerView

继承于ReclcyerView,扩展添加划至最后一项触发回调onLoading()事件

package fylder.recycler.demo.view;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

/**
 * 添加loading监听
 * <p/>
 * 添加手势滑动只能在向上滑触发
 * <p/>
 * Created by 剑指锁妖塔 on 15-11-24.
 */
public class FylderRecyclerView extends RecyclerView {

    boolean isDown = false; //手势是否下滑
    private LinearLayoutManager mLayoutManager;
    private GestureDetector mGestureDetector;

    boolean isLoading = false; //判断是否正在刷新或加载

    public FylderRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setLayoutManager(LayoutManager layout) {
        super.setLayoutManager(layout);
        if (layout instanceof LinearLayoutManager)
            this.mLayoutManager = (LinearLayoutManager) layout;
    }

    public boolean isLoading() {
        return isLoading;
    }

    public void setLoading(boolean loading) {
        isLoading = loading;
    }

    /**
     * 设置监听事件
     */
    public void setLoadingListener(final LoadingListener listener) {

        this.addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                int visibleItemCount = mLayoutManager.getChildCount();
                int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
                int totalItemCount = mLayoutManager.getItemCount();
                if (visibleItemCount > 1 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem >= totalItemCount - 1) {
                    if (!isDown && !isLoading()) {
                        setLoading(true);
                        listener.onLoading();
                    } else {
                        Log.w("123", "onLoading");
                    }
                }
            }
        });

        mGestureDetector = new GestureDetector(new GestureDetector.OnGestureListener() {

            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {

            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                //distanceY为负数则是向下
                if (distanceY < 0) {
                    isDown = true;
                } else {
                    isDown = false;
                }
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {

            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

                return false;
            }
        });

        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return mGestureDetector.onTouchEvent(event);
            }
        });
    }

    public interface LoadingListener {

        /**
         * 触发加载更多
         */
        void onLoading();
    }
}

BaseRecyclerAdapter

基类数据适配器的封装用一个抽象类,在使用这个自定义控件的adapter必须继承于此,目前首部head-lay不需要考虑,主要用于封装尾部footer-lay,至于如何显示有四种不同的情况由STATS判定。

package fylder.recycler.demo.adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;
import fylder.recycler.demo.R;
import fylder.recycler.demo.tools.AnimTools;


/**
 * Created by 剑指锁妖塔 on 16-4-29.
 */
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter {

    private static final int TYPE_FOOTER_VIEW = 100;//尾部布局类型
    private int extraCount = 1;//额外多出来的

    protected final int STATS_EMPTY = 1;      //  空白
    protected final int STATS_LOADING = 2;    //  加载
    protected final int STATS_LOADED = 3;     //  加载完了
    protected final int STATS_END = 4;        //  到最后一条

    Context context;

    protected int STATS = STATS_EMPTY;

    public BaseRecyclerAdapter(Context context) {
        this.context = context;
    }

    public void refresh(T t) {
        STATS = STATS_EMPTY;
        notifyDataSetChanged();
    }

    public void addListData(T t) {
        STATS = STATS_LOADED;
        notifyDataSetChanged();
    }

    /**
     * 正在加载
     */
    public void loading() {
        STATS = STATS_LOADING;
        notifyDataSetChanged();
    }

    /**
     * 最后一条
     */
    public void ending() {
        STATS = STATS_END;
        notifyDataSetChanged();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        switch (viewType) {
            case TYPE_FOOTER_VIEW:
                View footerView = LayoutInflater.from(context).inflate(R.layout.footer_lay, parent, false);
                return new FooterViewHolder(footerView);
            default:
                return createExcludeViewHolder(parent, viewType);
        }
    }

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

        if (getItemViewType(position) == TYPE_FOOTER_VIEW) {

            FooterViewHolder footerViewHolder = (FooterViewHolder) holder;

            if (STATS == STATS_LOADING) {
                //正在加载中
                footerViewHolder.lay.setVisibility(View.VISIBLE);
                footerViewHolder.loadingText.setVisibility(View.GONE);
                footerViewHolder.loadingPro.setVisibility(View.VISIBLE);
                footerViewHolder.loadingText.setText(R.string.recycler_loading);
                AnimTools.show(footerViewHolder.lay);
            } else if (STATS == STATS_LOADED) {
                footerViewHolder.lay.setVisibility(View.GONE);
            } else if (STATS == STATS_END) {
                //加载结束后
                footerViewHolder.lay.setVisibility(View.VISIBLE);
                footerViewHolder.loadingText.setVisibility(View.VISIBLE);
                footerViewHolder.loadingPro.setVisibility(View.GONE);
                footerViewHolder.loadingText.setText(R.string.recycler_load_end);
                AnimTools.show(footerViewHolder.lay);
            } else {
                //其余情况隐藏
                footerViewHolder.lay.setVisibility(View.GONE);
            }
        } else {
            onBindView(holder, position);
        }
    }

    /**
     * 获取该type的ViewHolder
     *
     * @param viewType
     * @return
     */
    public abstract RecyclerView.ViewHolder createExcludeViewHolder(ViewGroup parent, int viewType);

    @Override
    public int getItemCount() {
        return getExcludeItemCount() + extraCount;
    }

    @Override
    public int getItemViewType(int innerPosition) {
        if (getItemCount() - 1 == innerPosition) { // footer
            return TYPE_FOOTER_VIEW;
        } else {
            return getExcludeItemViewType(innerPosition);
        }
    }

    /**
     * 绑定数据
     *
     * @param holder
     */
    public abstract void onBindView(RecyclerView.ViewHolder holder, int position);

    /**
     * (不包括headerView和footerView)
     *
     * @return 获取item的数量
     */
    public abstract int getExcludeItemCount();

    /**
     * 通过realItemPosition得到该item的类型(不包括headerView和footerView)
     *
     * @param realItemPosition 位置
     * @return 得到该item的类型
     */
    public abstract int getExcludeItemViewType(int realItemPosition);

    class FooterViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.footer_lay)
        RelativeLayout lay;
        @BindView(R.id.footer_loading)
        ProgressBar loadingPro;
        @BindView(R.id.footer_text)
        TextView loadingText;

        public FooterViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

如何使用

DemoAdapter

只需要写显示的布局绑定,最后一项布局已在继承的BaseRecyclerAdapter,不需要考虑。

package fylder.recycler.demo.adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import butterknife.ButterKnife;
import fylder.recycler.demo.R;

/**
 * Created by 剑指锁妖塔 on 2016/4/29.
 */
public class DemoAdapter extends BaseRecyclerAdapter<Integer> {

    LayoutInflater inflater;

    int c;

    public DemoAdapter(Context context) {
        super(context);
        inflater = LayoutInflater.from(context);
        c = 0;
    }

    @Override
    public void refresh(Integer integer) {
        this.c = integer;
        super.refresh(integer);
    }

    @Override
    public void addListData(Integer integer) {
        this.c += integer;
        super.addListData(integer);
    }


    @Override
    public RecyclerView.ViewHolder createExcludeViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.item_demo, parent, false);
        return new DemoViewHolder(view);
    }

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

    }

    @Override
    public int getExcludeItemCount() {
        return c;
    }

    @Override
    public int getExcludeItemViewType(int realItemPosition) {
        return 0;
    }

    class DemoViewHolder extends RecyclerView.ViewHolder {

        public DemoViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

DemoActivity

package fylder.recycler.demo;

import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu;
import android.view.MenuItem;

import butterknife.BindView;
import butterknife.ButterKnife;
import fylder.recycler.demo.adapter.DemoAdapter;
import fylder.recycler.demo.adapter.FishAdapter;
import fylder.recycler.demo.view.FylderRecyclerView;

public class DemoActivity extends AppCompatActivity implements 
            SwipeRefreshLayout.OnRefreshListener, FylderRecyclerView.LoadingListener {

    @BindView(R.id.demo_refresh)
    SwipeRefreshLayout refreshLayout;
    @BindView(R.id.demo_recycler)
    FylderRecyclerView recyclerView;

    LinearLayoutManager manager;
    DemoAdapter mAdapter;

    Context mContext;
    Handler mHandler = new Handler(Looper.getMainLooper());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        ButterKnife.bind(this);
        mContext = this;
        init();
    }


    void init() {
        manager = new LinearLayoutManager(mContext);
        recyclerView.setLayoutManager(manager);
        mAdapter = new DemoAdapter(mContext);
        recyclerView.setAdapter(mAdapter);
        refreshLayout.setOnRefreshListener(this);   //刷新监听
        recyclerView.setLoadingListener(this);      //加载监听

        refreshLayout.setColorSchemeResources(R.color.color1, R.color.color2,
                R.color.color3, R.color.color4);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshLayout.setRefreshing(true);
                refresh();
            }
        }, 100);
    }

    /**
     * 刷新回调
     */
    @Override
    public void onRefresh() {
        refresh();
    }

    /**
     * 加载回调
     */
    @Override
    public void onLoading() {
        mAdapter.loading();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshLayout.setRefreshing(false);
                recyclerView.setLoading(false);

                if (mAdapter.getItemCount() > 21) {
                    mAdapter.ending();
                } else {
                    mAdapter.addListData(7);
                }
            }
        }, 2000);
    }

    void refresh() {
        if (!recyclerView.isLoading()) {
            recyclerView.setLoading(true);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    refreshLayout.setRefreshing(false);
                    recyclerView.setLoading(false);
                    mAdapter.refresh(7);
                }
            }, 1000);
        }
    }
}

为了支持网格列表GridLayoutManager,需要重写动态分配SpanSize的值,否则在加载的显示栅格占位不合理。

/**
 * 动态分配span size
 * Created by 剑指锁妖塔 on 2016/4/29.
 */
public class MoreLayoutManager extends GridLayoutManager {

    public MoreLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
        init(spanCount);
    }

    void init(final int spanCount) {

        //分配最后一个享有span size=2
        setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {

                if (position == getItemCount() - 1)
                    return spanCount;
                else {
                    return 1;
                }
            }
        });
    }
}

大鱼吃小鱼

有次无意中装了个 想去 app,发现里面有个加载动画的鲨鱼追小鱼挺有趣,就想做一个,然后花点时间弄了个自定义FishView,里面的动画轨迹用到赛贝尔曲线,我已将这个效果加入加载的RecyclerView的Demo里,详细源代码在Github RecyclerRefresh