在上一章中,我们了解了RecyclerView控件的基本用法,这里我们将实现RecyclerView的刷新和加载。

1. SwipeRefreshLayout下拉刷新

我们可以利用SwipeRefreshLayout控件来实现下拉刷新,详见Android SwipeRefreshLayout控件。

2. 自定义下拉刷新

我们首先创建一个下拉刷新的父类RefreshViewCreator,一般拥有四种状态,普通、下拉刷新、释放刷新和刷新。
主要方法如下

  • View onCreateRefreshView(Context context, ViewGroup parent),创建刷新界面
  • int move(int dy),用于下拉时改变界面
  • release(float dy),用于释放时改变界面
  • refreshFinish(),刷新结束,一般用于回调

具体代码如下,

public abstract class RefreshViewCreator {    public final static int DONE               = 0;    public final static int PULL_TO_REFRESH    = 1; // 下拉刷新    public final static int RELEASE_TO_REFRESH = 2; // 释放刷新    public final static int REFRESHING         = 3; // 刷新    public IOnRefreshListener mListener;    public RefreshViewCreator(IOnRefreshListener listener) {        this.mListener = listener;    }    // 创建刷新界面    public abstract View onCreateRefreshView(Context context, ViewGroup parent);    // 下拉时改变界面    public int move(int dy) {        return 0;    }    // 释放时改变界面    public void release(float dy) {    }    // 刷新结束    public void refreshFinish() {    }    protected void startRefresh() {        if (mListener != null) {            mListener.onRefresh();        }    }    public interface IOnRefreshListener {        void onRefresh();    }}

CustomRecyclerView继承RecyclerView,默认使用LinearLayoutManager布局。

public class CustomRecyclerView extends RecyclerView {    private LinearLayoutManager mLayoutManager;    private RefreshViewCreator mRefreshViewCreator;    private boolean mRecord;    private int mLastEventY;    private boolean mRefresh;    private int mTouchSlop;    public CustomRecyclerView(@NonNull Context context) {        this(context, null);    }    public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);        setLayoutManager(mLayoutManager);        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();    }    public void setRefreshViewCreator(RefreshViewCreator refreshViewCreator) {        this.mRefreshViewCreator = refreshViewCreator;    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                record(ev);                break;            case MotionEvent.ACTION_MOVE:                record(ev);                int dy = (int) (ev.getY() - mLastEventY);                mLastEventY = (int) ev.getY();                if (allowRefresh()) {                    if (dy > mTouchSlop) {                        mRefresh = true;                    }                    if (mRefresh) {                        int distance = mRefreshViewCreator.move(dy / 3);                        if (distance == 0) {                            ev.setAction(MotionEvent.ACTION_CANCEL);                            return dispatchTouchEvent(ev);                        }                        return true;                    }                }                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                if (mRefresh) {                    mRefreshViewCreator.release((int) (ev.getY() - mLastEventY) / 3);                    mRefresh = false;                    mLastEventY = 0;                    mRecord = false;                    return true;                }                break;        }        return super.dispatchTouchEvent(ev);    }    private void record(MotionEvent ev) {        if (allowRefresh() && !mRecord) {            mLastEventY = (int) ev.getY();            mRecord = true;        }    }    // 是否滚动到表头了    private boolean allowRefresh() {        return mRefreshViewCreator != null && !canScrollVertically(-1);    }}

我们通过allowRefresh()方法来判断是否已经滚动到表头了,这里有个问题是canScrollVertically()方法,当direction等于-1的时候,才是判断是否可以下拉。另外如果刷新界面高度是0或者gone的时候,canScrollVertically(-1)始终返回true。

/** * Check if this view can be scrolled vertically in a certain direction. * * @param direction Negative to check scrolling up, positive to check scrolling down. * @return true if this view can be scrolled in the specified direction, false otherwise. */public boolean canScrollVertically(int direction) {    final int offset = computeVerticalScrollOffset();    final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();    if (range == 0) return false;    if (direction < 0) {        return offset > 0;    } else {        return offset < range - 1;    }}

Activity里面,创建一个RefreshViewCreator,利用RecyclerViewAdapter加入到表头第一行,RecyclerViewAdapter可查看上一章。

private Handler mHandler = new Handler();private RefreshViewCreator mRefreshViewCreator;private RefreshViewCreator.IOnRefreshListener mListener = new RefreshViewCreator.IOnRefreshListener() {    @Override    public void onRefresh() {        mHandler.postDelayed(new Runnable() {            @Override            public void run() {                mRefreshViewCreator.refreshFinish();            }        }, 3000);    }};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_material_design_recycler_view_refresh);    CustomRecyclerView recyclerView = findViewById(R.id.recycler_view);    RecyclerViewAdapter adapter = new RecyclerViewAdapter(this);    recyclerView.setAdapter(adapter);    mRefreshViewCreator = new CustomRefreshViewCreator(mListener);    recyclerView.setRefreshViewCreator(mRefreshViewCreator);    adapter.addHeaderView(mRefreshViewCreator.onCreateRefreshView(this, recyclerView));}

CustomRefreshViewCreator继承RefreshViewCreator,实现自定义刷新界面

public class CustomRefreshViewCreator extends RefreshViewCreator {    private View mRefreshView;    private ImageView mIvArrow;    private ProgressBar mProgressBar;    private TextView mTvRefreshTips;    private String mPullToRefreshTips, mReleaseToRefreshTips, mRefreshingTips;    private int mContentHeight;    private int mHeadState = DONE;    private int mDistance = 0;    public CustomRefreshViewCreator(IOnRefreshListener listener) {        super(listener);        mPullToRefreshTips = "下拉刷新";        mReleaseToRefreshTips = "松开刷新";        mRefreshingTips = "正在刷新...";    }    @Override    public View onCreateRefreshView(Context context, ViewGroup parent) {        if (mRefreshView == null) {            mRefreshView = LayoutInflater.from(context).inflate(getLayoutId(), parent, false);            mContentHeight = context.getResources().getDimensionPixelOffset(R.dimen.margin_dpi_50);            mIvArrow = mRefreshView.findViewById(R.id.iv_arrow);            mProgressBar = mRefreshView.findViewById(R.id.progress_bar_refresh);            mTvRefreshTips = mRefreshView.findViewById(R.id.tv_refresh_tips);            refreshFinish();        }        return mRefreshView;    }    protected @LayoutRes int getLayoutId() {        return R.layout.list_view_refresh_view;    }    @Override    public int move(int dy) {        if (mHeadState != REFRESHING) {            mDistance += dy;            if (mDistance < 0) {                mDistance = 0;            } else if (mDistance > mContentHeight + 10) {                mDistance = mContentHeight + 10;            }            if (mDistance <= 0) {                mHeadState = DONE;            } else if (mDistance >= mContentHeight) {                mHeadState = RELEASE_TO_REFRESH;                mIvArrow.setRotation(180);            } else {                mHeadState = PULL_TO_REFRESH;                mIvArrow.setRotation(0);            }            if (mHeadState == PULL_TO_REFRESH || mHeadState == RELEASE_TO_REFRESH) {                int padding = mDistance - mContentHeight;                mRefreshView.setPadding(0, padding > 0 ? 0 : padding, 0, 0);            }            changeHeaderViewByState();        }        return mDistance;    }    @Override    public void release(float dy) {        if (mHeadState != REFRESHING) {            mDistance += dy;            if (mDistance >= mContentHeight) {                mHeadState = REFRESHING;            } else {                mHeadState = DONE;            }            mDistance = 0;            changeHeaderViewByState();            if (mHeadState == REFRESHING) {                startRefresh();            }        }    }    @Override    public void refreshFinish() {        mHeadState = DONE;        changeHeaderViewByState();    }    private void changeHeaderViewByState() {        switch (mHeadState) {            case DONE:                mRefreshView.setPadding(0, -1 * mContentHeight + 1, 0, 0);                mIvArrow.setVisibility(View.VISIBLE);                mProgressBar.setVisibility(View.GONE);                mTvRefreshTips.setText(mPullToRefreshTips);                break;            case PULL_TO_REFRESH:                mIvArrow.setVisibility(View.VISIBLE);                mProgressBar.setVisibility(View.GONE);                mTvRefreshTips.setText(mPullToRefreshTips);                break;            case RELEASE_TO_REFRESH:                mIvArrow.setVisibility(View.VISIBLE);                mProgressBar.setVisibility(View.GONE);                mTvRefreshTips.setText(mReleaseToRefreshTips);                break;            case REFRESHING:                mRefreshView.setPadding(0, 0, 0, 0);                mIvArrow.setVisibility(View.GONE);                mProgressBar.setVisibility(View.VISIBLE);                mTvRefreshTips.setText(mRefreshingTips);                break;        }    }}

刷新界面list_view_refresh_view.xml

<?xml version="1.0" encoding="utf-8"?>                                

显示如下

3. 自定义上拉加载

上拉加载的实现类似于刷新,同样需要上拉加载的父类LoadViewCreator

public abstract class LoadViewCreator {    public final static int DONE                = 0;    public final static int PULL_TO_LOAD        = 1; // 上拉加载    public final static int RELEASE_TO_LOAD     = 2; // 释放加载    public final static int LOADING             = 3; // 加载    public IOnLoadListener mListener;    public LoadViewCreator(IOnLoadListener listener) {        this.mListener = listener;    }    public abstract View onCreateLoadView(Context context, ViewGroup parent);    public int move(int dy) {        return 0;    }    public void release(float dy) {    }    public void loadFinish() {    }    protected void startLoad() {        if (mListener != null) {            mListener.onLoad();        }    }    public interface IOnLoadListener {        void onLoad();    }}

自定义LoadViewCreator的实现类似于CustomRefreshViewCreator,只需要注意的是我们设置bottompadding

mLoadView.setPadding(0, 0, 0, padding);

而在CustomRecyclerView里面,同样在dispatchTouchEvent()方法里面处理事件吗,只是在调用LoadViewCreatormove()和release()`方法,需要把参数的值转换成正数。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            record(ev);            break;        case MotionEvent.ACTION_MOVE:            record(ev);            int dy = (int) (ev.getY() - mLastEventY);            mLastEventY = (int) ev.getY();            if (allowRefresh()) {                if (dy > mTouchSlop) {                    mRefresh = true;                }                if (mRefresh) {                    int distance = mRefreshViewCreator.move(dy / 3);                    if (distance == 0) {                        ev.setAction(MotionEvent.ACTION_CANCEL);                        return dispatchTouchEvent(ev);                    }                    return true;                }            }            if (allowLoad()) {                if (-dy > mTouchSlop) {                    mLoad = true;                }                if (mLoad) {                    int distance = mLoadViewCreator.move(-dy);                    if (distance == 0) {                        ev.setAction(MotionEvent.ACTION_CANCEL);                        return dispatchTouchEvent(ev);                    }                    return true;                }            }            break;        case MotionEvent.ACTION_CANCEL:        case MotionEvent.ACTION_UP:            mRecord = false;            if (mRefresh) {                mRefreshViewCreator.release((int) (ev.getY() - mLastEventY) / 3);                mRefresh = false;                mLastEventY = 0;                return true;            }            if (mLoad) {                mLoadViewCreator.release((int) (mLastEventY - ev.getY()));                mLoad = false;                mLastEventY = 0;                return true;            }            break;    }    return super.dispatchTouchEvent(ev);}private void record(MotionEvent ev) {    if ((allowRefresh() || allowLoad()) && !mRecord) {        mLastEventY = (int) ev.getY();        mRecord = true;    }}private boolean allowLoad() {    return mLoadViewCreator != null && !canScrollVertically(1);}

显示如下

4. 自动上拉加载

当用户向上翻动列表,快要到所有列表项的底部或者已经到底部的时候,可以自动加载更多的数据。为此,我们需要监听RecyclerView列表的滚动,

public void addOnScrollListener(OnScrollListener listener)

同时也获取列表最下面的元素是哪一行,LinearLayoutManager为我们提供了很多接口

public int findFirstVisibleItemPosition()public int findFirstCompletelyVisibleItemPosition()public int findLastVisibleItemPosition()public int findLastCompletelyVisibleItemPosition()

实现代码如下

final LinearLayoutManager layoutManager = new LinearLayoutManager(this);RecyclerView recyclerView = findViewById(R.id.recycler_view);recyclerView.setLayoutManager(layoutManager);final RecyclerViewAdapter adapter = new RecyclerViewAdapter(this);recyclerView.setAdapter(adapter);View footerView = getLayoutInflater().inflate(R.layout.list_view_load_more_view, recyclerView, false);adapter.addFooterView(footerView);recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {    @Override    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {        if (!mLoadMore && layoutManager.findLastVisibleItemPosition() == adapter.getItemCount() - 1) {            mLoadMore = true;            mHandler.postDelayed(new Runnable() {                @Override                public void run() {                    adapter.addContent();                    mLoadMore = false;                }            }, 1000);        }    }});

显示如下

更多相关文章

  1. Android(安卓)GLSurfaceView.Renderer 类 onDrawFrame 方法的刷
  2. android 更新界面视图 Handler和runOnUiThread
  3. react native ——android返回键问题
  4. 【Android(安卓)界面效果13】关于全屏和取消标题栏
  5. android中利用 ViewPage 实现滑动屏
  6. Android使用Fragment嵌套Fragment的方式实现界面滑动
  7. 28. android——miniTwitter登录界面 详解
  8. 为Android2.3添加下拉通知栏的快捷开关
  9. android 音量调节以及媒体音量界面

随机推荐

  1. 编写App的开场Activity
  2. Android 手机上安装并运行 Ubuntu 12.04
  3. android media 播放器
  4. Android瀑布流的实现
  5. Android中JNI的使用之HelloWorld
  6. (连载)Android 8.0 : 系统启动流程之Linu
  7. Android开发(九)| android手势开发
  8. android:动态缩放和旋转图像
  9. Android(安卓)textview字体颜色显示和图
  10. Android中的基础----如何获得LinearLayou