自定义通用RecyclerView上拉加载与下拉刷新&为RecyclerView添加Adapter

 

  • 概述

RecyclerView是一个比ListView更加灵活的View,它通过设置LayoutManager来布局子item,常见的LayoutManager有LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager(交错布局)。那么我们应该怎样来实现通用的RecyclerView上拉加载与下拉刷新呢?

  • 实现思路

在看实现思路之前先看看最终的效果图:第一次制作gif图哈,效果有点问题,里面的所有图片都来自gank.io,还有我自己的练习项目Gank.IO正在开发中欢迎start和给我提一些建议。这个是我的git地址:https://github.com/xiaolutang/GankIo.git。项目现在十分的low希望以后有机会让它变得更加好吧。


扯了那么多的蛋终于要说到实现思路了,首先我们通过重写RecyclerView的Adapter类未recyclerView添加头部和尾部。如何添加Apdater的思想我就不自己写了,这里我参考学习了鸿洋大神的一片文章有兴趣可以自己看看Android 优雅的为RecyclerView添加HeaderView和FooterView地址是https://blog.csdn.net/lmj623565791/article/details/51854533。在下拉的时候通过设置顶部view的marginTop来实现下拉效果。同理尾部通过改变尾部view的marginBottom来实现效果。

  • 整体结构设计类图

自定义通用RecyclerView上拉加载与下拉刷新&为RecyclerView添加Adapter_第1张图片

IRefreshListener:这几接口定义了RecyclerView加载更多和刷新方法。当下拉刷新和上拉加载的时候回自动调用,

IPullRefresh:这个接口定义了v头部和尾部添加的view的状态更改和变化。

AbsPullRefreshView:是一个抽象类,实现了IPullRefresh接口

CommonPullRefreshView: 通用的头部和尾部

PullRefreshRecyclerView:能够实现上拉加载和下拉刷新的RecyclerView

  • 基本实现

PullRefreshRecyclerView:复写setAdapter方法,在AdapterWrapter中保存实际的adapter,RecyclerView的adapter实际上是AdapterWrapter对象

public class PullRefreshRecyclerView extends RecyclerView {

    private final String TAG = PullRefreshRecyclerView.class.getSimpleName();
    /**
     * 阻尼效果
     */
    private final float OFFSET_RADIO = 1.5f;

    /**
     * 是否可以下拉   默认可以
     */
    private boolean mEnablePullRefresh = true;


    private Context context;
    private AbsPullRefreshView mHeader;
    private AbsPullRefreshView mFooter;

    private AdapterWrapper adapterWrapper;
    private OnPullRefreshListener listener;

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

    public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this( context, attrs, 0);
    }

    public PullRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super( context, attrs, defStyle );
        this.context = context;
    }

    private void init(){
        mHeader = new CommonPullRefreshView(context,this,AbsPullRefreshView.VIEW_TYPE_HEADER);
        mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
        mFooter = new CommonPullRefreshView( context,this,AbsPullRefreshView.VIEW_TYPE_FOOTER );
        adapterWrapper.AddHeaderView( mHeader.getView( context,this ) );
        adapterWrapper.addFootView( mFooter.getView( context,this ) );
    }

    @Override
    public void setAdapter(Adapter adapter) {
        adapterWrapper = new AdapterWrapper( adapter );
        adapter.registerAdapterDataObserver( new AdapterDataObserver() {
            @Override
            public void onChanged() {
                adapterWrapper.notifyDataSetChanged();
            }

            @Override
            public void onItemRangeChanged(int positionStart, int itemCount) {
                adapterWrapper.notifyItemRangeChanged( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
            }

            @Override
            public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                adapterWrapper.notifyItemRangeChanged( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
            }

            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                adapterWrapper.notifyItemRangeInserted( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
            }

            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                adapterWrapper.notifyItemRangeRemoved( positionStart+adapterWrapper.mHeaderViews.size(),itemCount );
            }

            @Override
            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                adapterWrapper.notifyDataSetChanged();
            }
        } );
        init();
        super.setAdapter( adapterWrapper );

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent( ev );
    }

    int mLastRawX, mLastRawY;
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if(!mEnablePullRefresh){
            return super.onTouchEvent( e );
        }
        int rawX = (int) e.getRawX();
        int rawY = (int) e.getRawY();
        switch (e.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastRawX = (int) e.getRawX();
                mLastRawY = (int) e.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetY = (int) (e.getRawY() - mLastRawY);
                Log.e( TAG,"onTouchEvent offsetY  = "+offsetY +" isSlideToTop "+isSlideToTop() +" isSlideToBottom "+isSlideToBottom());
                if(isSlideToTop() && offsetY>0){
                    mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_PULL );
                    mHeader.setViewMarginTop( (int) (offsetY / OFFSET_RADIO) );
                }else if(isSlideToBottom() && offsetY<0){
                    mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_PULL );
                    mFooter.setViewMarginBottom( (int) -(offsetY / OFFSET_RADIO) );
                }
//                mLastRawX = rawX;
//                mLastRawY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                mLastRawX = -1;
                mLastRawY = -1;
                if(mFooter.getViewState() == AbsPullRefreshView.VIEW_STATE_PULL){
                    mFooter.setViewMarginBottom( 0 );
                    if(listener!=null){
                        mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
                        listener.loadMore();
                    }
                }else if(mHeader.getViewState() == AbsPullRefreshView.VIEW_STATE_PULL){
                    mHeader.setViewMarginTop( 0 );
                    if(listener!=null){
                        mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_RUNNING );
                        listener.onRefresh();
                    }
                }
                break;
        }
        return super.onTouchEvent( e );
    }

    private boolean isSlideToTop(){
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager instanceof StaggeredGridLayoutManager){
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
            int firstCompletelyVisibleItemPositions[] = new int[staggeredGridLayoutManager.getSpanCount()];
            firstCompletelyVisibleItemPositions = staggeredGridLayoutManager.findFirstCompletelyVisibleItemPositions( firstCompletelyVisibleItemPositions );
            if(firstCompletelyVisibleItemPositions[0] == 0 || firstCompletelyVisibleItemPositions[0]  == 1){
                return true;
            }
        }
        if(layoutManager instanceof LinearLayoutManager){
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            int position = linearLayoutManager.findFirstVisibleItemPosition();
            if(position <= 1){
                return true;
            }else {
                return false;
            }
        }
        return !canScrollVertically(1);
    }

    private boolean isSlideToBottom() {
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager instanceof StaggeredGridLayoutManager){
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;

            int spanCount = staggeredGridLayoutManager.getSpanCount();
            int lastCompletelyVisibleItemPositions[] = new int[spanCount];
            lastCompletelyVisibleItemPositions = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(lastCompletelyVisibleItemPositions  );
            for (int i=0; i= adapterWrapper.getRealItemCount()-staggeredGridLayoutManager.getSpanCount() || !canScrollVertically(-1)){
                    Log.e( TAG,"isBottom" );
                    return true;
                }
            }
            return false;
        }
        if(layoutManager instanceof LinearLayoutManager){
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            int position = linearLayoutManager.findLastCompletelyVisibleItemPosition();
            if(position >= adapterWrapper.getItemCount()-2){
                return true;
            }else {
                return false;
            }
        }
        Log.e( TAG,"canScrollVertically  "+canScrollVertically(-1) );
        return !canScrollVertically(-1);
    }

    public void setOnPullRefreshListener(OnPullRefreshListener listener){
        this.listener = listener;
    }

    public void setRefreshFinish(){

        mHeader.updateViewState( AbsPullRefreshView.VIEW_STATE_NORMAL );
    }

    public void setLoadMoreFinish(){
        mFooter.updateViewState( AbsPullRefreshView.VIEW_STATE_NORMAL);
    }

    private class AdapterWrapper extends RecyclerView.Adapter{

        private static final int BASE_ITEM_TYPE_HEADER = 100000;
        private static final int BASE_ITEM_TYPE_FOOTER = 200000;
        //头集合 尾结合
        private SparseArrayCompat mHeaderViews = new SparseArrayCompat<>();
        private SparseArrayCompat mFootViews = new SparseArrayCompat<>();

        private RecyclerView.Adapter mInnerAdapter;

        public AdapterWrapper(Adapter mInnerAdapter) {
            this.mInnerAdapter = mInnerAdapter;
        }

        private boolean isHeaderViewPos(int position){
            return position < mHeaderViews.size();
        }

        public void AddHeaderView(View view){
            mHeaderViews.put( mHeaderViews.size()+BASE_ITEM_TYPE_HEADER,view );
        }

        private boolean isFooterViewPos(int position){
            return position >= mHeaderViews.size()+mInnerAdapter.getItemCount();
        }

        public int getHeadersCount(){
            return mHeaderViews.size();
        }

        public int getFootersCount(){
            return mFootViews.size();
        }

        public void addFootView(View view){
            mFootViews.put( mFootViews.size()+BASE_ITEM_TYPE_FOOTER,view );
        }

        public int getRealItemCount(){
            return mInnerAdapter.getItemCount();
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if(mHeaderViews.get( viewType ) != null){
                return new HeaderViewHolder(mHeaderViews.get( viewType )  );
            }else if(mFootViews.get( viewType ) != null){
                return new FootViewHolder( mFootViews.get( viewType ) );
            }
            return mInnerAdapter.onCreateViewHolder( parent,viewType );
        }

        @Override
        public int getItemViewType(int position) {
            if(isHeaderViewPos( position )){
                return mHeaderViews.keyAt( position );
            }else if(isFooterViewPos( position )){
                return mFootViews.keyAt( position - mHeaderViews.size() - mInnerAdapter.getItemCount() );
            }
            return mInnerAdapter.getItemViewType(position - mHeaderViews.size());
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            if (isHeaderViewPos(position)) {

                return;
            }
            if (isFooterViewPos(position)) {
                return;
            }
            mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
        }

        @Override
        public int getItemCount() {
            return mFootViews.size()+mHeaderViews.size()+mInnerAdapter.getItemCount();
        }

        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
//            mInnerAdapter.onAttachedToRecyclerView(recyclerView);

            /**
             * 解决网格布局问题
             */
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;

                gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        int viewType = getItemViewType(position);
                        if (mHeaderViews.get(viewType) != null) {
                            return gridLayoutManager.getSpanCount();
                        } else if (mFootViews.get(viewType) != null) {
                            return gridLayoutManager.getSpanCount();
                        } else {
                            return 1;
                        }
                    }
                });
            }
        }


    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        mInnerAdapter.onViewAttachedToWindow(holder);
        int position = holder.getLayoutPosition();
        if (isHeaderViewPos(position) || isFooterViewPos(position)) {
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

            if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {

                StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
                p.setFullSpan(true);
            }
            if(lp != null && lp instanceof GridLayoutManager.LayoutParams){
                GridLayoutManager.LayoutParams p = (GridLayoutManager.LayoutParams) lp;
            }
        }
    }


        private class HeaderViewHolder extends RecyclerView.ViewHolder {
            HeaderViewHolder(View itemView) {
                super(itemView);
            }
        }

        private class FootViewHolder extends RecyclerView.ViewHolder {
            FootViewHolder(View itemView) {
                super(itemView);

            }
        }

    }

    public interface OnPullRefreshListener {
        void onRefresh();
        void loadMore();
    }
}
public interface IPullRefresh {
    int viewState = -1;
    View getView(Context context, ViewGroup parent);
    void onPull(int currentHeight, int refreshHeight, int state);
    void onRunning();
    void onStopRunning();
}
public abstract class AbsPullRefreshView implements IPullRefresh {
    public static final int VIEW_TYPE_HEADER = 0;
    public static final int VIEW_TYPE_FOOTER = 1;

    /**
     * view的类型,头
     * */
    protected int viewType = VIEW_TYPE_HEADER;

    /**
     * 未被初始化
     * */
    protected static final int VIEW_STATE_UNINIT = -1;
    protected static final int VIEW_STATE_NORMAL = 0;
    protected static final int VIEW_STATE_PULL = 1;
    protected static final int VIEW_STATE_RELEASE = 2;
    protected static final int VIEW_STATE_RUNNING = 3;

    private int viewState = VIEW_STATE_NORMAL;

    public AbsPullRefreshView(int viewType) {
        this.viewType = viewType;
    }

    public abstract void setViewMarginTop(int marginTop);

    public abstract void setViewMarginBottom(int marginBottom);

    protected void updateViewState(int viewState){
        this.viewState = viewState;
    }

    public int getViewState(){
        return viewState;
    }

}
//这个是我自己设计的通用头部和尾部,如果有其他的效果可以继承AbsPullRefreshView自己来实现,

public class CommonPullRefreshView extends AbsPullRefreshView {

    private final String TAG = CommonPullRefreshView.class.getSimpleName();

    /**
     *根布局
     */
    private View mContainer;

    private ImageView mImageView;

    private TextView mTitleTextView;

    private Animation rotateAnimation;

    public CommonPullRefreshView(int viewType) {
        super( viewType );
    }

    public CommonPullRefreshView(Context context, ViewGroup parent, int viewType) {
        super( viewType );
        getView( context,parent );
    }

    @Override
    public View getView(Context context, ViewGroup parent) {
        if(mContainer != null){
            return mContainer;
        }
        mContainer = LayoutInflater.from( context ).inflate( R.layout.pull_refresh_header, parent,false);
        mImageView = mContainer.findViewById( R.id.pull_refresh_header_ImageView );
        mTitleTextView = mContainer.findViewById( R.id.pull_refresh_header_title_TextView );
        rotateAnimation = AnimationUtils.loadAnimation( context,R.anim.refreshing_rotate );
        rotateAnimation.setRepeatCount(Animation.INFINITE);
        rotateAnimation.setRepeatMode(Animation.RESTART);
        updateViewState(VIEW_STATE_NORMAL);
        return mContainer;
    }

    @Override
    public void onPull(int currentHeight, int refreshHeight, int state) {
        mImageView.setVisibility( View.VISIBLE );
        switch (this.viewType){
            case VIEW_TYPE_HEADER:
                mImageView.setImageResource( R.drawable.ic_up_48 );
                mTitleTextView.setText( "释放刷新数据" );
                break;
            case VIEW_TYPE_FOOTER:
                mImageView.setImageResource( R.drawable.ic_down_48 );
                mTitleTextView.setText( "释放加载更多" );
        }
    }

    @Override
    public void onRunning() {
        updateViewState(VIEW_STATE_RUNNING);
    }

    @Override
    public void onStopRunning() {
        mImageView.setVisibility( View.GONE );
        updateViewState(VIEW_STATE_NORMAL);
    }

    @Override
    public void setViewMarginTop(int marginTop) {
        if(mContainer == null){
            throw new NullPointerException( "mContainer is null in setViewMarginTop do you have call getView" );
        }
        ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams();
        marginLayoutParams.topMargin = marginTop;
        mContainer.setLayoutParams( marginLayoutParams );
    }

    @Override
    public void setViewMarginBottom(int marginBottom) {
        if(mContainer == null){
            throw new NullPointerException( "mContainer is null in setViewMarginBottom do you have call getView" );
        }
        ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams();
        marginLayoutParams.bottomMargin = marginBottom;
        mContainer.setLayoutParams( marginLayoutParams );
    }

    @Override
    protected void updateViewState(int viewState) {
        super.updateViewState( viewState );
        switch (viewState){
            case VIEW_STATE_NORMAL:
                if(viewType == VIEW_TYPE_HEADER){
                    mTitleTextView.setText( "下拉刷新" );
                    mImageView.clearAnimation();
                    mImageView.setImageResource( R.drawable.ic_up_48 );
                }else if(viewType == VIEW_TYPE_FOOTER){
                    mTitleTextView.setText( "上拉加载更多" );
                    mImageView.clearAnimation();
                    mImageView.setImageResource( R.drawable.ic_down_48);
                }
                mContainer.getLayoutParams().height = 0;
                mContainer.requestLayout();
                mContainer.setVisibility( View.GONE);
                break;
            case VIEW_STATE_PULL:
            case VIEW_STATE_RELEASE:
                if(viewType == VIEW_TYPE_HEADER){
                    mTitleTextView.setText( "松开刷新" );
                    mImageView.clearAnimation();
                    mImageView.setImageResource( R.drawable.ic_up_48 );
                }else if(viewType == VIEW_TYPE_FOOTER){
                    mTitleTextView.setText( "松开加载" );
                    mImageView.clearAnimation();
                    mImageView.setImageResource( R.drawable.ic_down_48);
                }
                mContainer.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                mContainer.requestLayout();
                mContainer.setVisibility( View.VISIBLE);
                break;
            case VIEW_STATE_RUNNING:
                mTitleTextView.setText( "正在加载。。。" );
                mImageView.setImageResource( R.drawable.ic_refreshing_48 );
                mImageView.setAnimation( rotateAnimation );
                mImageView.startAnimation( rotateAnimation );
                mContainer.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                mContainer.requestLayout();
                mContainer.setVisibility( View.VISIBLE);
                break;

        }
    }
}

通用的布局:




    

    

这样就实现了基本实现了通用的添加头部和底部。具体效果可以在这个里面查看:

https://github.com/xiaolutang/androidTool.git

自定义通用RecyclerView上拉加载与下拉刷新&为RecyclerView添加Adapter_第2张图片

你可能感兴趣的:(android)