自定义RecyclerView实现下拉刷新和上拉加载


通过导入第三方库实现ListView的上拉加载和下拉刷新比较简单,今天我要讲的是自定义RecyclerView实现下拉刷新和上拉加载。首先,自定义下拉刷新上拉加载是通过给RecyclerView添加头部和尾部实现的。而问题是RecyclerView并没有addHeaderView(View v)和addFooterView(View v)方法。

     第一步:自定义HeaderAndFooterWrapper(装饰者模式)实现给RecyclerView添加头部和尾部
    方法:public void addHeaderView(View v)
              public void addFooterView(View v)
     代码参照张鸿洋:Android 优雅的为RecyclerView添加HeaderView和FooterView
     链接:http://blog.csdn.net/lmj623565791/article/details/51854533

      第二步:定义类RefreshRecyclerView并抽取为库

1) 头部布局:refresh_recyclerview_header.xml

<?xml version="1.0" encoding="utf-8"?>
<!--下拉刷新控件-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <ImageView
            android:id="@+id/iv_header_refresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@drawable/headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pb_header_refresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/custom_progressbar"
            android:visibility="gone" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_status"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:text="下拉刷新"
            android:textColor="#ff0000"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:gravity="center_horizontal"
            android:text="上次更新时间:2016-10-31"
            android:textColor="#55000000"
            android:textSize="16sp" />

    </LinearLayout>

</LinearLayout>


2)尾部布局(上拉加载部分):refresh_recyclerview_footer.xml
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ProgressBar
        android:indeterminateDrawable="@drawable/custom_progressbar"
        android:layout_margin="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:text="加载更多中...."
        android:textColor="#ff0000"
        android:textSize="25sp"
        android:layout_marginLeft="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout></span>


3)自定义ProgressBar样式custom_progressbar.xml
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%">
    <shape android:shape="ring"
        android:innerRadiusRatio="2.5"
        android:thicknessRatio="15"
        android:useLevel="false"
        >
        <gradient android:startColor="#ff0000"
            android:endColor="#ffffff"
            android:centerColor="#88ff0000"
            android:type="sweep"></gradient>
    </shape>
</rotate></span>
代码如下:

public class RefreshRecyclerView extends RecyclerView {

    private final Context mContext;
//    顶部视图,下拉刷新控件
    private LinearLayout headerView;
//    正在刷新状态的进度条
    private ProgressBar pb_header_refresh;
//    刷新箭头
    private ImageView iv_header_refresh;
//    显示刷新状态
    private TextView tv_status;
//    显示最近一次的刷新时间
    private TextView tv_time;
//    转到下拉刷新状态时的动画
    private RotateAnimation downAnima;
//     转到释放刷新状态时的动画
    private RotateAnimation upAnima;
    //触摸事件中按下的Y坐标,初始值为-1,为防止ACTION_DOWN事件被抢占
    private float startY = -1;
//    下拉刷新控件的高度
    private int pulldownHeight;
    //    刷新状态:下拉刷新
    private final int PULL_DOWN_REFRESH = 0;
    //    刷新状态:释放刷新
    private final int RELEASE_REFRESH = 1;
    //    刷新状态:正常刷新
    private final int REFRESHING = 2;

    //    当前头布局的状态-默认为下拉刷新
    private int currState = PULL_DOWN_REFRESH;
//
    private RefreshRecyclerView.OnRefreshListener mOnRefreshListener;
//    尾部视图
    private View footerView;
//    尾部试图(上拉加载控件)的高度
    private int footerViewHeight;
//    判断是否是加载更多
    private boolean isLoadingMore;

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

    public RefreshRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RefreshRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initHeaderView();
        initFooterView();
    }
    /**
     * 返回尾部布局,供外部调用
     * @return
     */
    public View getFooterView(){
        return footerView;
    }
    /**
     * 返回头部布局,供外部调用
     * @return
     */
    public View getHeaderView(){
        return headerView;
    }

    /**
     * 通过HeaderAndFooterWrapper对象给RecyclerView添加尾部
     * @param footerView 尾部视图
     * @param headerAndFooterWrapper RecyclerView.Adapter的包装类对象,通过它给RecyclerView添加尾部视图
     */
    public void addFooterView(View footerView, HeaderAndFooterWrapper headerAndFooterWrapper) {
        headerAndFooterWrapper.addFooterView(footerView);
    }

    /**
     * 通过HeaderAndFooterWrapper对象给RecyclerView添加头部部
     * @param headerView 尾部视图
     * @param headerAndFooterWrapper RecyclerView.Adapter的包装类对象,通过它给RecyclerView添加头部视图
     */
    public void addHeaderView(View headerView,HeaderAndFooterWrapper headerAndFooterWrapper) {
        headerAndFooterWrapper.addHeaderView(headerView);
    }


 初始化头部布局和尾部布局 
  
 
 private void initHeaderView() {
        headerView = (LinearLayout) View.inflate(mContext, R.layout.refresh_recyclerview_header, null);
        tv_time = (TextView) headerView.findViewById(R.id.tv_time);
        tv_status = (TextView) headerView.findViewById(R.id.tv_status);
        iv_header_refresh = (ImageView) headerView.findViewById(R.id.iv_header_refresh);
        pb_header_refresh = (ProgressBar) headerView.findViewById(R.id.pb_header_refresh);
        headerView.measure(0, 0);
        pulldownHeight = headerView.getMeasuredHeight();
        headerView.setPadding(0, -pulldownHeight, 0, 0);
        //初始化头部布局的动画
        initAnimation();
    }
 /**
     * 刷新状态改变时的动画
     */
    private void initAnimation() {
//        从下拉刷新状态转换为释放刷新状态
        upAnima = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        upAnima.setFillAfter(true);
        upAnima.setDuration(500);
//         转到下拉刷新的动画
        downAnima = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        downAnima.setFillAfter(true);
        downAnima.setDuration(500);
    }

 
 
 private void initFooterView() {
        footerView = View.inflate(mContext, R.layout.refresh_recyclerview_footer, null);
        footerView.measure(0, 0);
        //得到控件的高
        footerViewHeight = footerView.getMeasuredHeight();
        //默认隐藏下拉刷新控件
        // View.setPadding(0,-控件高,0,0);//完全隐藏
        //View.setPadding(0, 0,0,0);//完全显示
        footerView.setPadding(0, -footerViewHeight, 0, 0);
//        addFooterView(footerView);
//        自己监听自己
        this.addOnScrollListener(new MyOnScrollListener());
    }

 
 
重写onTouchEvent()方法,startY表示按下时的坐标,但是有可能ACTION_DOWN事件被强占,那么case MotionEvent.ACTION_DOWN:就没机会执行。所以case MotionEvent.ACTION_MOVE:子句时首先判断startY是否为-1,是的话把第一次移动的坐标当作按下时的坐
标。然后判断当前的状态,如果正在刷新就不必再次执行下面的刷新代码,直接跳出。dY>0表示用户正在执行下拉操作,因为头部视图初始坐标为-pulldownHeight,所以手指在屏幕上滑动多少距离,顶部视图的y坐标就应该增加相应值。paddingTop==0是一个状态
分界线,paddingTop < 0 是下拉刷新状态,大于0是释放刷新状态。根据paddingTop的值跳转相应的状态。最后在手指抬起的时候,首先重置startY = -1;然后判断刷新状态,如果是下拉刷新(头部视图没有完全显示)就设置下拉控件为默认隐藏状态,如果是释放
刷新状态(paddingTop > 0),就跳转到正在刷新状态,并让下拉控件完全显示同时调用用户的回调事件,刷新页面数据。
 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //防止ACTION_DOWN事件被抢占,没有执行
                if (startY == -1) {
                    startY = ev.getY();
                }
                float endY = ev.getY();
                float dY = endY - startY;
                //判断当前是否正在刷新中
                if (currState == REFRESHING) {
                    //如果当前是正在刷新,不执行下拉刷新了,直接break;
                    break;
                }
//                如果是下拉
                if (dY > 0) {
                    int paddingTop = (int) (dY - pulldownHeight);
                    if (paddingTop > 0 && currState != RELEASE_REFRESH) {
                        //完全显示下拉刷新控件,进入松开刷新状态
                        currState = RELEASE_REFRESH;
                        refreshViewState();
                    } else if (paddingTop < 0 && currState != PULL_DOWN_REFRESH) {
                        //没有完全显示下拉刷新控件,进入下拉刷新状态
                        currState = PULL_DOWN_REFRESH;
                        refreshViewState();
                    }
                    headerView.setPadding(0, paddingTop, 0, 0);
                }
                break;
            case MotionEvent.ACTION_UP:
                //5.重新记录值
                startY = -1;
                if (currState == PULL_DOWN_REFRESH) {
                    //设置默认隐藏
                    headerView.setPadding(0, -pulldownHeight, 0, 0);
                } else if (currState == RELEASE_REFRESH) {
                    //当前是释放刷新,进入到正在刷新状态,完全显示
                    currState = REFRESHING;
                    refreshViewState();
                    headerView.setPadding(0, 0, 0, 0);
                    //调用用户的回调事件,刷新页面数据
                    if (mOnRefreshListener != null) {
                        mOnRefreshListener.onPullDownRefresh();
                    }
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
 /**
     * 跳转刷新状态
     */
    private void refreshViewState() {
        switch (currState) {
//            跳转到下拉刷新
            case PULL_DOWN_REFRESH:
                iv_header_refresh.startAnimation(downAnima);
                tv_status.setText("下拉刷新");
                break;
//            跳转到释放刷新
            case RELEASE_REFRESH:
                iv_header_refresh.startAnimation(upAnima);
                tv_status.setText("释放刷新");
                break;
//            跳转到正在刷新
            case REFRESHING:
                iv_header_refresh.clearAnimation();
                iv_header_refresh.setVisibility(GONE);
                pb_header_refresh.setVisibility(VISIBLE);
                tv_status.setText("正在刷新中.....");
                break;
        }
    }
 定义接口 
 
/**
 * 定义下拉刷新和上拉加载的接口
 */
public interface OnRefreshListener {
    /**
     * 当下拉刷新时触发此方法
     */
    void onPullDownRefresh();

    /**
     * 当加载更多的时候回调这个方法
     */
    void onLoadingMore();

}
public void setOnRefreshListener(RefreshRecyclerView.OnRefreshListener listener) {
        this.mOnRefreshListener = listener;
 }

 /**
     * 当刷新完数据之后,调用此方法,把头文件隐藏,并且状态设置为初始状态
     * @param isSuccess
     */
    public void onFinishRefresh(boolean isSuccess) {
        if (isLoadingMore) {
            footerView.setPadding(0, -footerViewHeight, 0, 0);
            isLoadingMore = false;
        } else {
            headerView.setPadding(0, -pulldownHeight, 0, 0);
            currState = PULL_DOWN_REFRESH;
            iv_header_refresh.setVisibility(VISIBLE);
            pb_header_refresh.setVisibility(GONE);
            tv_status.setText("下拉刷新");
            if (isSuccess) {
                //设置更新时间
                tv_time.setText(getSystemTime());
            }
        }
    }

    private String getSystemTime() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return dateFormat.format(new Date());
    }
 
 
 
 上拉加载逻辑的实现,重写onScrollStateChanged()方法。如果newState为空闲状态或者是快速滑动状态就判断RecyclerView的最后一个可见Item的索引是否 >= getChildCount()-1。是的话就触发加载更多事件,完全显示上拉加载控件并调用回调事件。这里要注意:ListView是SCROLL_STATE_FLING而RecyclerView是SCROLL_STATE_SETTLING,ListView得到最后一个可见Item索引直接 
 
getLastVisiblePosition()方法就行了。RecyclerView比较麻烦:首先要判断布局管理器是否是线性布局管理器,因为只有LinearLayoutManager才有查找第一个和最后一个可见View位置的方法。

 private class MyOnScrollListener extends OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//            super.onScrollStateChanged(recyclerView, newState);
            if (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING) {
                //判断是当前layoutManager是否为LinearLayoutManager
                // 只有LinearLayoutManager才有查找第一个和最后一个可见view位置的方法
                LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager instanceof LinearLayoutManager) {
                    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                    //当停止滚动时或者惯性滚动时,RecyclerView的最后一个显示的条目:getCount()-1
//                    注意是findLastVisibleItemPosition()而不是getLastVisiblePosition
                    if (linearLayoutManager.findLastVisibleItemPosition() >= getChildCount() - 1) {
                        isLoadingMore = true;
                        //把底部加载显示
                        footerView.setPadding(0, 0, 0, 0);
                        if (mOnRefreshListener != null) {
                            mOnRefreshListener.onLoadingMore();
                        }
                    }
                }
            }
        }
    }

      第三步:使用 自定义的RecyclerView
首先上布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.refreshrecyclerview_library.RefreshRecyclerView
        android:id="@+id/custom_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:cacheColorHint="@android:color/transparent"
        android:divider="@null"
        android:fadingEdge="none" />

</LinearLayout>

测试代码

public class MainActivity extends Activity {


    private static final int REFRESH = 0;
    private static final int LOADMORE = 1;
    private HeaderAndFooterWrapper headerAndFooterWrapper;
    private RefreshRecyclerViewAdapter recyclerAdapter;
    private RefreshRecyclerView custom_recyclerview;
    private Handler handler = new Handler(){
        public void handleMessage(Message msg){
            switch (msg.what) {
                case REFRESH:
                    custom_recyclerview.onFinishRefresh(true);
                    break;
                case LOADMORE:
                    custom_recyclerview.onFinishRefresh(false);
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        custom_recyclerview = (RefreshRecyclerView) findViewById(R.id.custom_recyclerview);
        initRefreshRecyclerView();
    }

    private void initRefreshRecyclerView() {
//        给Recycler设置分割线
        custom_recyclerview.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
        recyclerAdapter = new RefreshRecyclerViewAdapter(this);
        headerAndFooterWrapper = new HeaderAndFooterWrapper(recyclerAdapter);
//        不要忘记设置布局管理器
        custom_recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        custom_recyclerview.setAdapter(headerAndFooterWrapper);
        custom_recyclerview.addHeaderView(custom_recyclerview.getHeaderView(), headerAndFooterWrapper);
        custom_recyclerview.addFooterView(custom_recyclerview.getFooterView(), headerAndFooterWrapper);
        custom_recyclerview.setOnRefreshListener(new OnRecyclerRefreshListener());
    }

    private class OnRecyclerRefreshListener implements RefreshRecyclerView.OnRefreshListener {
        @Override
        public void onPullDownRefresh() {
//           执行下拉刷新操作,一般是联网更新数据
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(2000);
                    handler.sendEmptyMessage(REFRESH);
                }
            }).start();
        }

        @Override
        public void onLoadingMore() {
//            执行上拉加载操作,一般是联网请求更多分页数据
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SystemClock.sleep(2000);
                    handler.sendEmptyMessage(LOADMORE);
                }
            }).start();
        }
    }
}
<pre name="code" class="java">
public class RefreshRecyclerViewAdapter extends RecyclerView.Adapter<RefreshRecyclerViewAdapter.ViewHolder> {

    private Context mContext;
    private List<String> datas;

    public RefreshRecyclerViewAdapter(Context mContext) {
        this.mContext = mContext;
        datas = new ArrayList<>();
       for(int i = 1; i <= 20; i++) {
         datas.add("我是Content :"+i);
       }
    }

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

        View convertView = View.inflate(mContext, R.layout.item_recycler, null);
        return new ViewHolder(convertView);
    }

    @Override
    public void onBindViewHolder(RefreshRecyclerViewAdapter.ViewHolder holder, final int position) {
        String data = datas.get(position);
        holder.tv_content.setText(data);
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView tv_content;

        public ViewHolder(final View itemView) {
            super(itemView);
            tv_content = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}

 
 
 这里还用到一个分割线的类,RecyclerView默认是没有分割线的,这段代码可以直接粘贴使用 
 
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    public final static int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    public final static int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    private int mOrientation;
    private Drawable mDivider;
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};

    public DividerItemDecoration(Context context, int mOrientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(mOrientation);

    }

    private void setOrientation(int mOrientation) {
        if (mOrientation != VERTICAL_LIST && mOrientation != HORIZONTAL_LIST) {
            throw new IllegalArgumentException("Invalid orientation");
        } else {
            this.mOrientation = mOrientation;
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else if (mOrientation == HORIZONTAL_LIST) {
            drawHorizontal(c, parent);
        }
    }

    private void drawVertical(Canvas c, RecyclerView parent) {
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    private void drawHorizontal(Canvas c, RecyclerView parent) {
        int top = parent.getPaddingTop();
        int bottom = parent.getHeight() - parent.getPaddingBottom();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int left = child.getRight() + params.rightMargin;
            int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left,top,right,bottom);
            mDivider.draw(c);
        }

    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//        super.getItemOffsets(outRect, view, parent, state);
        if(mOrientation == VERTICAL_LIST) {
            outRect.set(0,0,0,mDivider.getIntrinsicHeight());
        }else{
            outRect.set(0,0,mDivider.getIntrinsicHeight(),0);
        }
    }
}

github代码链接: https://github.com/18895612697/CustomRefreshRecyclerView

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值