RecyclerView作為ListView,GridView的升級版,使用起來非常靈活。并且配合動畫可以實現非常贊的效果。
基本使用步驟:
mRecyclerView = findView(R.id.id_recyclerview);
//設置布局管理器
mRecyclerView.setLayoutManager(layout);
//設置adapter
mRecyclerView.setAdapter(adapter)
//設置Item增加、移除動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割線
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
1.基礎知識點
- LayoutManager 顧名思義 負責布局的管理,通過切換布局管理器我們可以輕松實現列表,網格,瀑布流等效果。
- Adapter 用于適配item,這里的Adapter是繼承自RecyclerView.Adapter不是BaseAdapter
- ItemDecoration 通俗點講就是“分割線”,類似listView中的divider,但是RecyclerView中并未提供這個屬性,要實現分割線,需要通過調用addItemDecoration(),系統并未提供缺省的ItemDecoration實現類。幸運的是已經有第三方實現好了的ItemDecoration,后面介紹
- ItemAnimator 有了它可以實現各種炫酷的動畫效果,系統提供了缺省的DefaultItemAnimator
2.知識點詳解
⑴ LayoutManager 系統提供了LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager,分別對應三種效果 列表,網格,瀑布流。示例代碼:
mNormalRecyclerView.setLayoutManager(new LinearLayoutManager(this));//設置list布局
mNormalRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));//設置網格布局
mNormalRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));//設置瀑布流布局
可以看到想要切換效果只需要設置下LayoutManager,對于有需求要求顯示多種布局效果的時候,用RecyclerView相比listView等要省力靈活很多。
...
也許這還不夠打動你,接著往下看
通常我們的listView,GirdView都是豎直方向流向的,需求來了要實現橫向的listView腫么辦?過去還是要花點力氣去實現的吧,看RecyclerView分分鐘秒殺你
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mNormalRecyclerView.setLayoutManager(layoutManager);//設置list橫向布局
⑵ Adapter 適配item布局的東東看代碼,先寫個最簡單的Adapter
public class AdapterNormal extends RecyclerView.Adapter<AdapterNormal.MyViewHolder> {
private Context context;
private List<String> list;
public AdapterNormal(Context context, List<String> list) {
this.context = context;
this.list = list;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//產生幾個可復用的ViewHolder實例
MyViewHolder viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
return viewHolder;
}
@Override
public int getItemCount() {
return list.size();
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {//為每一項View綁定數據
holder.mTextView.setText(list.get(position));
}
class MyViewHolder extends RecyclerView.ViewHolder {//ViewHolder 大家都不陌生
private TextView mTextView;
public MyViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.mText);
}
}
}
調用它
adapterNormal = new AdapterNormal(getApplicationContext(), list);
mNormalRecyclerView.setAdapter(adapterNormal);//設置適配器
多個不同項布局,Adapter該怎么寫?
@Override
public int getItemViewType(int position) {//在Adapter中重寫該方法,根據條件返回不同的值例如100,101
if (...) {
return 100;
}else if (...) {
return 101;
}else{
return super.getItemViewType(position);
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//根據getItemViewType返回的值生成不同的ViewHolder實例
MyViewHolder viewHolder = null;
switch (viewType) {//示例邏輯
case 100:
viewHolder=...;
break;
case 101:
viewHolder=...;
break;
default:
viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
break;
}
return viewHolder;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {//為不同的布局適配數據
switch (holder.getItemViewType()) {
case 100:
...
break;
case 101:
...
break;
default:
holder.mTextView.setText(list.get(position));
break;
}
}
每次都這樣寫Adapter是不是覺得很累?簡化它
首先引入 compile 'com.zhy:base-adapter:2.0.0'
Android 萬能的Adapter for ListView,RecyclerView,GridView等,支持多種Item類型的情況。
mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{
@Override
public void convert(ViewHolder holder, String s)
{
holder.setText(R.id.id_item_list_title, s);
}
});
是不是相當方便,在convert方法中完成數據、事件綁定即可。還有多種ItemViewType的封裝等自行研究都很方便
⑶ItemDecoration "分割線" 這玩意感覺沒什么好說的直接看代碼
/**
* ListView的分割線
*/
public class ListItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public ListItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final 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) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
/**
* @author 鴻洋
* GridView的分割線
* 因為作者瀑布流的分割線在item高度不一樣的情況下有bug,所以被我去掉了
*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
public GridItemDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent) {
// 列數
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
return spanCount;
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private boolean isLastColum(int pos, int spanCount) {
if ((pos + 1) % spanCount == 0)// 如果是最后一列,則不需要繪制右邊
{
return true;
}
return false;
}
private boolean isLastRaw(int pos, int spanCount, int childCount) {
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一行,則不需要繪制底部
return true;
return false;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent.getChildAdapterPosition(view), spanCount, childCount))// 如果是最后一行,則不需要繪制底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent.getChildAdapterPosition(view), spanCount))// 如果是最后一列,則不需要繪制右邊
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
}
⑷ ItemAnimator 前面說過了可以產生炫酷的動畫,提升逼格的神器
https://github.com/wasabeef/recyclerview-animators
⑸ “遺憾”。 是的RecyclerView并沒有像listView,GridView那樣提供itemClickListener和itemLongClickListener,我們需要自行實現,可以通過設置接口回調,好消息是上面提到的Android 萬能的Adapter 已經為我們實現好了這些工作。
⑹ 補充。當數據發生變化時我們需要更新數據集
adapterNormal.notifyDataSetChanged(); //無動畫效果
adapterNormal.notifyItemInserted(0);//有動畫效果
以上就是RecyclerView的基本使用,華麗的分割線,提升。。。
自定義RecyclerView,實現下拉刷新,上拉加載更多
https://github.com/jianghejie/XRecyclerView
看效果先
源碼淺析:
public class XRecyclerView extends RecyclerView {
@Override
public void setAdapter(Adapter adapter) {//重寫適配器方法
mWrapAdapter = new WrapAdapter(adapter);//一個包裝類
super.setAdapter(mWrapAdapter);//設置這個包裝后的適配器作為適配器
adapter.registerAdapterDataObserver(mDataObserver);//注冊自定義的數據觀察者,因為適配器是包裝后的適配器
mDataObserver.onChanged();
}
@Override
public void onScrollStateChanged(int state) {//通過判斷最后一個可見item的位置和項數量的大小關系實現上拉加載更多
super.onScrollStateChanged(state);
if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) {
LayoutManager layoutManager = getLayoutManager();
int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
lastVisibleItemPosition = findMax(into);
} else {
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
if (layoutManager.getChildCount() > 0
&& lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() > layoutManager.getChildCount() && !isNoMore && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
View footView = mFootViews.get(0);
isLoadingData = true;
if (footView instanceof LoadingMoreFooter) {
((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_LOADING);
} else {
footView.setVisibility(View.VISIBLE);
}
mLoadingListener.onLoadMore();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {//通過重寫觸摸事件實現下拉刷新
if (mLastY == -1) {
mLastY = ev.getRawY();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
if (isOnTop() && pullRefreshEnabled) {
mRefreshHeader.onMove(deltaY / DRAG_RATE);
if (mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
return false;
}
}
break;
default:
mLastY = -1; // reset
if (isOnTop() && pullRefreshEnabled) {
if (mRefreshHeader.releaseAction()) {
if (mLoadingListener != null) {
mLoadingListener.onRefresh();
}
}
}
break;
}
return super.onTouchEvent(ev);
}
}
private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {//定義一個包裝類,將外部自定義的適配器和內部適配器邏輯關聯起來
private RecyclerView.Adapter adapter;//持有我們使用的時候自定義的適配器
public WrapAdapter(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {//只執行一次
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {//設置監聽回調只需要設置一次
@Override
public int getSpanSize(int position) {//SpanSize 代表占幾列
return (isHeader(position) || isFooter(position))//頭部或者尾部占滿全行或者全列
? gridManager.getSpanCount() : 1;
}
});
}
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {//被多次調用
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& (isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);//頭部或者尾部占滿全行或者全列
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_REFRESH_HEADER) {//根據返回的類型生成不同的ViewHolder實例
mCurrentPosition++;
return new SimpleViewHolder(mHeaderViews.get(0));
} else if (isContentHeader(mCurrentPosition)) {
if (viewType == sHeaderTypes.get(mCurrentPosition - 1)) {
mCurrentPosition++;
return new SimpleViewHolder(mHeaderViews.get(headerPosition++));
}
} else if (viewType == TYPE_FOOTER) {
return new SimpleViewHolder(mFootViews.get(0));
}
return adapter.onCreateViewHolder(parent, viewType);
}
private int mCurrentPosition;
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//為我們自定義的item類型綁定數據
if (isHeader(position)) {
return;
}
int adjPosition = position - getHeadersCount();
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
adapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}
@Override
public int getItemViewType(int position) {//返回刷新頭類型,普通頭類型,尾部類型,我們自定義的類型
if (isRefreshHeader(position)) {
return TYPE_REFRESH_HEADER;
}
if (isHeader(position)) {
position = position - 1;
return sHeaderTypes.get(position);
}
if (isFooter(position)) {
return TYPE_FOOTER;
}
int adjPosition = position - getHeadersCount();
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
return adapter.getItemViewType(adjPosition);
}
}
return TYPE_NORMAL;
}
}
private class DataObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {//數據改變的時候通過比較item數量顯示隱藏EmptyView
Adapter<?> adapter = getAdapter();
if (adapter != null && mEmptyView != null) {
int emptyCount = 0;
if (pullRefreshEnabled) {
emptyCount++;
}
if (loadingMoreEnabled) {
emptyCount++;
}
if (adapter.getItemCount() == emptyCount) {
mEmptyView.setVisibility(View.VISIBLE);
XRecyclerView.this.setVisibility(View.GONE);
} else {
mEmptyView.setVisibility(View.GONE);
XRecyclerView.this.setVisibility(View.VISIBLE);
}
}
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
}
};
大致思路:定義一個包裝適配器,通過不同的item類型判斷生成刷新頭,普通頭部,尾部,我們自定義的itemView并綁定item數據→監聽滾動和觸摸事件來顯示隱藏刷新頭和加載更多尾部視圖并通過接口拋出刷新和加載更多抽象方法