RecyclerView使用攻略(刷新篇)

小序:《RecyclerView使用攻略(助力篇)》之后,一直没有更新上下拉刷新的功能实现,主要还是受限于个人现有的技术实力,总觉得没有经过实际打磨的,就不敢有上场的自信。虽说现在已经引用到自己的项目上了,但还是得继续跟进优化的。

助力篇主要讲的啥

  1. RecyclerView 中 Adapter、ViewHolder 的使用
  2. RecyclerView 展示分割符、头部与顶部 View (集合)
  3. 自定义继承 RecyclerView 与属性封装

刷新篇又涉及哪些

此次刷新篇顾名思义就是实现 RecyclerView 的上下刷新及加载更多,与其他绝大多数开发者一样:仅结合官方组件 SwipeRefreshLayout 实现下拉刷新,通过重写函数 OnScrollListener 来监听 RecyclerView 滚动至底部时加载更多的展示:

URecyclerView to show

下拉刷新 上拉加载更多

为了方便回调监听,先定一个统一管理的接口来实现:

  1. 列表下拉刷新监听
  2. 列表上拉加载更多监听
  3. 列表上拉加载更多状态变化监听(方便自定义上拉加载更多动画状态处理)
private URecyclerRefreshListener mListener;

public interface URecyclerRefreshListener {

    void onRefresh();

    void onLoad(int pageIex);

    /**
     * @param stats <ul>{@link ULoadingView }
     *              <li>LOAD_START_加载更多</li>
     *              <li>LOAD_CLICK_点击加载更多</li>
     *              <li>LOAD_CLICK_已无更多加载</li></ul>
     */
    void loadStats(int stats);
}

下拉刷新引用 SwipeRefreshLayout 实现,同以往一样需要在 .xml 声明。不一样的是,这里为了实现上下拉动作监听的统一处理,只要将实例化的 SwipeRefreshLayout 传递给我们定义的 URecyclerView.setRefreshLayout(); 实现绑定就可以了,为了偷懒还需实例化回调监听。具体看下代码便知。

  1. 绑定 SwipeRefreshLayout 下拉刷新,URecyclerRefreshListener 回调监听
  2. 设置下拉控件箭头颜色(可在 .xml 下直接定义)
  3. 设置下拉动作监听
public void setRefreshLayout(SwipeRefreshLayout refreshLayout,
                         final URecyclerRefreshListener listener) {
    if (null == refreshLayout || null == listener) return;

    mRefreshLayout = refreshLayout;
    mListener = listener;

    if (null != mColors) {
        mRefreshLayout.setColorSchemeColors(mColors);
    }

    mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            if (null == mListener) return;

            mPageIndex = 1;
            mListener.onRefresh();
        }
    });
}

而上拉加载更多其实也很简单,通过重写 OnScrollListener 监听判断列表是否滚动至底部即可。借鉴

OnScrollListener mScroll = new OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        // 得到当前显示的最后一个 item 的 view
        View lastChildView = recyclerView.getLayoutManager().getChildAt(
                recyclerView.getLayoutManager().getChildCount() - 1);
        if (null == lastChildView) return;

        // 得到 lastChildView 的bottom 坐标值
        int lastChildBottom = mOrientation == VERTICAL ?
                lastChildView.getBottom() : lastChildView.getRight();

        // 得到 RecyclerView 的底部坐标减去底部 padding 值,也就是显示内容最底部的坐标
        int recyclerBottom = mOrientation == VERTICAL ?
                (recyclerView.getBottom() - recyclerView.getPaddingBottom()) :
                (recyclerView.getRight() - recyclerView.getPaddingRight());

        // 通过这个 lastChildView 得到这个 view 当前的 position 值
        int lastPosition = recyclerView.getLayoutManager().getPosition(lastChildView);

        // 判断 lastChildView 的bottom 值跟 recyclerBottom
        // 判断 lastPosition 是不是最后一个 position
        // 如果两个条件都满足则说明是真正的滑动到了底部
        if (lastChildBottom == recyclerBottom &&
                lastPosition == recyclerView.getLayoutManager().getItemCount() - 1) {
        }
    }
};

余下还得看看接口 URecyclerViewInterface.java 的几个重要函数调用,包括启动数据加载以及结束加载等操作。

/**
 * 启动数据刷新加载
 *
 * @param pageIndex 页码
 */
void refreshLoadStart(int pageIndex);

/**
 * 启动数据刷新加载
 */
void refreshLoadStart();

/**
 * 结束数据刷新加载,刷新,加载更多成功时调用
 */
void refreshLoadSuccess();

/**
 * 结束数据刷新加载,刷新,加载更多成功时调用
 *
 * @param isLoadMoreEnd true_结束上拉加载(已全部加载完成)
 */
void refreshLoadSuccessIsEnd(boolean isLoadMoreEnd);

/**
 * 结束数据刷新加载,刷新,加载更多失败时调用
 */
void refreshLoadFail();

/**
 * true_禁用下拉刷新
 */
void disRefresh(boolean isHideRefresh);

/**
 * true_禁用上拉加载更多
 */
void disLoadMore(boolean isDisLoadMore);

如果你需要获取当前加载页页码,可以直接通过 URecyclerView 的实例直接获取 mPageIndex,另外为了将获取到的数据区分开来处理(即更新数据与追加数据两种情况)。在成功加载完数据后可直接调用接口 RvDataInterface 下的函数执行数据更新,如下

 /**
  * 更新数组数据
  *
  * @param pageIndex 加载页面页码
  * @param list
  */
 void updateForPage(int pageIndex, List<T> list);

自定义上拉加载动画

在整个上拉加载更多的操作中,其实最受关注的应该是动画的设定。有经验的人应该很快能发现 Demo 当中用到的加载动画源自 jack wang 的 AVLoadingIndicatorView 。其实我也曾考虑过自定义动画,但不经意对于 Airbnb 下开源项目的动画效果赞不绝口,就以此为借口开溜了说。

回到正文上,由于自定义 URecyclerView 的依赖上有 AVLoadingIndicatorView 并已设置为默认(后期会去掉该依赖),故如有需要可直接引用创建动画。

/**
 * 设置加载更多视图
 *
 * @param view
 */
public void setLoadMoreView(View view) {
    mLoadMoreView = view;
}

/**
 * 设置加载更多视图(默认调用)
 */
public void setLoadMoreView() {
    ULoadingView loadingView = new ULoadingView(getContext());

    // 确保水平滚动列表加载更多布局与 Item 方向一致
    if (mOrientation == HORIZONTAL) {
        loadingView.setHorizontalShow();
    }

    setLoadMoreView(loadingView);
}

自定义空列表提示视图

我们都知道 ListView 有个 setEmptyView(View view) 的函数,当列表处于无数据的环境下显示 view 来提示用户当前状况以及下一步操作。 但是 RecyclerView 需要自己提供,其解决思路也很简单。需要注意的是,只有在空列表提示视图继承自 TextView 的情况下文本提示才会生效,所以如果自定义了其他内容的仍需要自行判断空数据显示内容。

  1. 设置提示视图与内容(包括网络状态、数据状态)
  2. Adapter 刷新数据 isEmpty() == true 显示,否则隐藏(首次刷新前默认不需要判断显示)
  3. 判断是否属于网络异常引起的问题,否则提示重新操作
public void setEmptyView(View emptyView) {
    isShowEmptyHide = true;

    if (!isURecyclerVAdapter()) {
        this.mEmptyView = emptyView;
    } else {
        BaseRVAdapter adapter = ((BaseRVAdapter) getAdapter());
        adapter.mEmptyView = emptyView;

        adapter.mEmptyView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 下拉刷新的不需要点击重新加载更多
                if (null == mListener || null != mRefreshLayout) return;

                mListener.onRefresh();
            }
        });
    }
}

/**
 * 默认调用
 */
public void setEmptyView() {
    TextView textView = new TextView(getContext());
    textView.setGravity(Gravity.CENTER);
    textView.setTextSize(18);
    textView.setTextColor(ULoadingView.sDefaultColor);
    textView.setPadding(0, 0, 0, 80);

    textView.setLayoutParams(new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));

    setEmptyView(textView);
}

/**
 * 设置提示语
 *
 * @param networkFailStr 网络异常时提示
 * @param dataEmptyStr   空数据列表提示
 */
public void setEmptyHide(String networkFailStr, String dataEmptyStr) {
    mNetworkFailStr = networkFailStr;
    mDataEmptyStr = dataEmptyStr;

    if (isURecyclerVAdapter()) {
        BaseRVAdapter adapter = (BaseRVAdapter) getAdapter();
        adapter.mNetworkFailStr = networkFailStr;
        adapter.mDataEmptyStr = dataEmptyStr;
    }
}

/**
 * 设置提示语
 *
 * @param networkFailId 网络异常时提示
 * @param dataEmptyId   空数据列表提示
 */
public void setEmptyHide(int networkFailId, int dataEmptyId) {
    Resources resources = getContext().getResources();

    setEmptyHide(resources.getString(networkFailId), resources.getString(dataEmptyId));
}

预设属性值设置

与助力篇一样,为了省去声明 RecycleView 而需要在类当中加入一系列的属性预设。我们同样为以上功能提供了力所能及的自定义属性引用。具体如下

NameFormatFunction
colorsreference下拉刷新颜色变换数组array,默认无
isFixSizeboolean是否固定列表项视图大小,默认固定
loadMoreTypeenumauto主动加载更多,autoHide主动加载更多并隐藏,click点击加载更多,clickHide点击加载更多并隐藏,默认不提供加载更多
emptyTypeenumnone不显示空数据提示,text显示空数据文本提示,默认none
spanCountinteger网格布局显示列数,默认2列
divider_marginLeftreference分隔符对应资源Drawable
divider_marginTopdimension分隔符左边距,默认0
divider_marginRightdimension分隔符右边距,默认0
divider_marginTopdimension分隔符顶部边距,默认0
divider_marginBottomdimension分隔符底部边距,默认0
divider_backgroundcolor分隔符边距底色,默认无
textNetworkFailstring/reference网络错误提示语
textDataEmptystring/reference空数据内容提示语
layoutManagerTypeenumlist列表类型,grid网格类型,staggeredGrid,瀑布流类型
orientationenumvertical垂直方向类型,horizontal水平方向列表

从我的小栗子经过

有时候看一堆内容后很容易觉得蒙圈甚至有些许倦意,所以盛上栗子(实际上涉及的代码篇幅感觉太多,就不一一贴出来了)。

arrays.xml

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

    <integer-array name="refresh_colors">
        <item>@color/colorPrimary</item>
        <item>@color/origin</item>
        <item>@color/colorAccent</item>
    </integer-array>

</resources>

strings.xml

<resources>
    <string name="app_name">URecycleView</string>
    <string name="text_network_fail">网络不给力吖~</string>
    <string name="text_data_empty">尝试下拉找回你的数据吧~</string>
</resources>

fragment_rv.layout

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

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/main_sw_ly"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.urun.undroidlib.view.URecyclerView
            android:id="@+id/main_rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            urv:colors="@array/refresh_colors"
            urv:divider_background="@color/white"
            urv:divider_drawable="@drawable/recycle_view_divider"
            urv:divider_marginLeft="16dp"
            urv:divider_marginRight="16dp"
            urv:layoutManagerType="list"
            urv:emptyType="text"
            urv:loadMoreType="click"
            urv:textDataEmpty="@string/text_data_empty"
            urv:textNetworkFail="@string/text_network_fail"/>

    </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

RecycleVFragment.java

public class RecycleVFragment extends Fragment {

    private URecyclerView mMainRv;
    private SwipeRefreshLayout mRefreshLayout;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_rv, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mMainRv = (URecyclerView) view.findViewById(R.id.main_rv);
        mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.main_sw_ly);

        initRecycleView();
    }

    /**
     * 实例化列表视图
     */
    private void initRecycleView() {
        RecycleVAdapter adapter = new RecycleVAdapter(getActivity(), new ArrayList());
        adapter.addItemClickListener(new BaseRVAdapter.OnItemClickListener() {
            @Override
            public void itemClick(int position) {

            }
        });

        mMainRv.setAdapter(adapter);
        mMainRv.setRefreshLayout(mRefreshLayout, mRefresh);
    }

    /**
     * 模拟接口请求操作
     */
    private void loadData(int pageIndex) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 2000);
    }

    URecyclerView.URecyclerRefreshListener mRefresh = new URecyclerView.URecyclerRefreshListener() {
        @Override
        public void onRefresh() {
            loadData(1);
        }

        @Override
        public void onLoad(int pageIex) {
            loadData(pageIex);
        }

        @Override
        public void loadStats(int stats) {

        }
    };
}

末尾旁白

项目地址:https://github.com/gzejia/URecyclerView

末尾的这个旁白就我个人认为,是非常有必要的。毕竟像这个东西最终只是实现了效果,但其肯定存在许多需要优化的地方,或是不合理的逻辑处理 以及错误的实现方式。一整个下来除了自己就没有别人了(希望接下来的团队开发能将它扶植得愈来愈好),所以如果有哪位亲在参考了这个 Demo 以后有任何意见的,还请尽情吐槽。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值