最近工作中用到了RecyclerView,所以写一篇关于RecyclerView的总结文章热热身

本文主要实现3主要功能

  • item上下移动与滑动删除
  • emptyView与RecyclerView的简单绑定
  • RecyclerView实现流畅分页加载

功能不是很复杂,稍微介绍下之后就直接上代码。

RecyclerView实现item上下移动、滑动删除


先看下效果图

android recyclerview设置分割线颜色 recycleview分页_拖拽

这个功能主要通过ItemTouchHelper.Callback和ItemTouchHelper这两个类实现。ItemTouchHelper是官方支持包提供的一个辅助RecyclerView实现动态拖拽的工具库。ItemTouchHelper.Callback会接收ItemTouchHelper的操作回调,通过这个类可以使得用户的触摸行反馈到对应的ViewHodler上。ItemTouchHelper要与ItemTouchHelper.Callback、RecyclerView配合使用。关键代码如下

newsAdapter = new NewsAdapter();
ItemTouchHelper.Callback callback = new MyItemTouchHelper(newsAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recyclerView);

MyItemTouchHelper继承了ItemTouchHelper.Callback,当用户操作完毕之后要通知adapter去更新数据与UI,这里使用了接口的方式,让NewsAdapter继承了自定义的OnItemMoveListener。MyItemTouchHelper代码如下

public class MyItemTouchHelper extends ItemTouchHelper.Callback {
    private static final String TAG = "MyItemTouchHelper";
    private OnItemMoveListener moveListener;

    public MyItemTouchHelper(OnItemMoveListener onItemMoveListener) {
        moveListener = onItemMoveListener;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int upFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;

        return makeMovementFlags(upFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        moveListener.onItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        Log.e(TAG, "onMove: " + viewHolder.getAdapterPosition() + ",target position: " + target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        moveListener.onItemRemoved(viewHolder.getAdapterPosition());
        Log.e(TAG, "onSwiped: " + viewHolder.getAdapterPosition());
    }

NewsAdapter继承了OnItemMoveListener,并且实现了onItemMoved和onSwiped。在onItemMoved中需要进行数据交换和UI更新,在onSwiped需要进行数据删除与UI更新,关键代码如下

public class NewsAdapter extends RecyclerView.Adapter<NewsHolder> implements OnItemMoveListener {
    ···
    @Override
    public void onItemMoved(int fromPosition, int toPosition) {
         //数据交换
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(dataList, i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(dataList, i, i - 1);
            }
        }
        notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onItemRemoved(int position) {
        //数据删除
        dataList.remove(position);
        notifyItemRemoved(position);
    }
    ···
}

emptyView的简单实现


一般RecyclerView中没有数据时需要展示一个emptyView,本次使用RecyclerView.AdapterDataObserver来实现此功能。
RecyclerView.AdapterDataObserver时一个能够监听adapter数据变化的观察者

看下布局

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.afun.recycleviewdemo.MainActivity">

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="gone">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@drawable/empty_view_bg" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:paddingBottom="8dp"
            android:paddingTop="4dp"
            android:text="没东西啦~~"
            android:textColor="#727272"
            android:textSize="16sp" />
    </LinearLayout>

    <com.afun.recycleviewdemo.ui.MyRecyclerView
        android:id="@+id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbarSize="5dp"
        android:scrollbarThumbVertical="@color/colorAccent"
        android:scrollbars="vertical">

    </com.afun.recycleviewdemo.ui.MyRecyclerView>


</android.support.design.widget.CoordinatorLayout>

这新建了一个AdapterObserver类并且继承了RecyclerView.AdapterDataObserver,通过这个类通知RecyclerView是否展示emptyView

public class AdapterObserver extends RecyclerView.AdapterDataObserver {
    private MyRecyclerView mRecyclerView;

    public void onAttach(MyRecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    @Override
    public void onChanged() {
        super.onChanged();
        mRecyclerView.showEmptyView();
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        super.onItemRangeInserted(positionStart, itemCount);
        mRecyclerView.showEmptyView();
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        super.onItemRangeRemoved(positionStart, itemCount);
        mRecyclerView.showEmptyView();
    }
}

然后将AdapterObserver与RecyclerView关联起来

//设置数据观察者
AdapterObserver observer = new AdapterObserver();
observer.onAttach(recyclerView);
recyclerView.onAttach(findViewById(R.id.emptyView), observer);

最后看下MyRecyclerView的关键代码

public class MyRecyclerView extends RecyclerView {
    ···
    public void onAttach(View emptyView, AdapterObserver adapterObserver) {
        mEmptyView = emptyView;
        mObserver = adapterObserver;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            mObserver.onChanged();
        }
    }

    public void showEmptyView() {
        Adapter<?> adapter = getAdapter();
        if (adapter != null && mEmptyView != null) {
            if (adapter.getItemCount() == 0) {
                mEmptyView.setVisibility(VISIBLE);
                this.setVisibility(GONE);
            } else {
                mEmptyView.setVisibility(GONE);
                this.setVisibility(VISIBLE);
            }
        }
    }
}

RecyclerView实现流畅分页加载


现在很多app都是分页加载,主要是通过重写recyclerView的OnScrollListener,看下介绍

public abstract static class OnScrollListener {
    /**
     * Callback method to be invoked when RecyclerView's scroll state changes.
     *
     * @param recyclerView The RecyclerView whose scroll state has changed.
     * @param newState     The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
     *                     {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
     */
    public void onScrollStateChanged(RecyclerView recyclerView, int newState){}

    /**
     * Callback method to be invoked when the RecyclerView has been scrolled. This will be
     * called after the scroll has completed.
     * <p>
     * This callback will also be called if visible item range changes after a layout
     * calculation. In that case, dx and dy will be 0.
     *
     * @param recyclerView The RecyclerView which scrolled.
     * @param dx The amount of horizontal scroll.
     * @param dy The amount of vertical scroll.
     */
    public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
}

使用下来,感觉onScrollStateChanged比较适合拖动到最后展示加载更多View(带个progressBar的View),然后再显示新的一页的场景,onScrolled适合大幅度滑动实现流畅加载更多的场景,本次主要使用onScrolled来实现自动加载更多,效果图如下

android recyclerview设置分割线颜色 recycleview分页_List_02

本次涉及的关键代码如下

//实现加载更多
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        final int childCount = recyclerView.getChildCount();
        if (childCount > 0) {
            View lastChild = recyclerView.getChildAt(childCount - 1);
            RecyclerView.Adapter outerAdapter = recyclerView.getAdapter();
            int lastVisible = recyclerView.getChildAdapterPosition(lastChild);
            if (lastVisible >= outerAdapter.getItemCount() - 3) {
                if (dataSource.canLoadMore()) {
                    ++current;
                    loadData();
                }
            }
        }
    }
});

private void loadData() {
        int length = dataSource.getLoadLength(current);
        newsAdapter.notifyLoadMoreChange(dataSource.getNewsList(currentPosition, length));
        currentPosition = currentPosition + length;
}

//newsAdapter.notifyLoadMoreChange方法
public void notifyLoadMoreChange(List<NewsBean.NewsListBean> dataList) {
    if (dataList != null && dataList.size() != 0) {
        this.dataList.addAll(dataList);

        int oldSize = getItemCount();
        //使用notifyItemRangeInserted插入新数据
        notifyItemRangeInserted(oldSize, this.dataList.size());
    }
}

OK,全部介绍完毕,最后附上代码有兴趣的同学可以看看。