RecyclerView 現(xiàn)在可以說是很常用了吧?RecyclerView 自然是很方便的控件朗若,但用多了有時(shí)候?qū)σ恍┲貜?fù)性代碼也是感覺挺麻煩的创肥,于是乎我就將一些重復(fù)性代碼封裝了起來矿酵,從而使 RecyclerView 的使用更加的簡便
本篇博客包含的內(nèi)容有:
- 通用的單布局 RecyclerView.Adapter
- 通用的多布局 RecyclerView.Adapter
- 通用的 RecyclerView.ItemDecoration
- RecyclerView 的單擊和長按事件監(jiān)聽
- 帶頭部與底部View的 RecyclerView
- SnapHelper 的使用
一浪箭、通用 RecyclerView.Adapter
CommonRecyclerViewAdapter 是一個(gè)抽象類,利用泛型構(gòu)造了一個(gè)通用的Adapter下梢,并通過 MultiTypeSupport 接口來實(shí)現(xiàn)對多布局的支持客蹋。
此外,有時(shí)候我們在刷新數(shù)據(jù)時(shí)孽江,改變的數(shù)據(jù)可能只是List集合中的單個(gè)數(shù)據(jù)讶坯,如果都采用 Adapter.notifyDataSetChanged() 來刷新整個(gè)視圖,無疑是浪費(fèi)資源的岗屏,此處采用了 DiffUtil 來對比前后兩個(gè)數(shù)據(jù)集辆琅,尋找出舊數(shù)據(jù)集與新數(shù)據(jù)集的最小變化量,從而對數(shù)據(jù)進(jìn)行定向刷新这刷,可以只刷新相應(yīng)的Item婉烟,使得視圖的刷新過程更為高效,且增添和刪除數(shù)據(jù)時(shí)都伴隨有相應(yīng)的動(dòng)畫效果
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:50
* 說明:通用RecyclerView Adapter
*/
public abstract class CommonRecyclerViewAdapter<T> extends RecyclerView.Adapter<CommonRecyclerViewHolder> {
//省略一些代碼
......
private DiffUtil.Callback callback = new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return getItemCount();
}
@Override
public int getNewListSize() {
return newDataList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return CommonRecyclerViewAdapter.this.areItemsTheSame(oldItemPosition, newItemPosition);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return CommonRecyclerViewAdapter.this.areContentsTheSame(oldItemPosition, newItemPosition);
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return CommonRecyclerViewAdapter.this.getChangePayload(oldItemPosition, newItemPosition);
}
};
public void setData(final List<T> dataList) {
new Thread(new Runnable() {
@Override
public void run() {
newDataList.clear();
newDataList = CommonRecyclerViewAdapter.this.clone(dataList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback, true);
Message message = new Message();
message.what = DIFF_UTIL_UPDATE;
message.obj = diffResult;
handler.sendMessage(message);
}
}).start();
}
@Override
public int getItemViewType(int position) {
if (multiTypeSupport != null) {
return multiTypeSupport.getLayoutId(dataList.get(position), position);
}
return DEFAULT_ITEM_VIEW_TYPE;
}
@Override
public CommonRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (multiTypeSupport != null) {
layoutId = viewType;
}
return new CommonRecyclerViewHolder(layoutInflater.inflate(layoutId, parent, false));
}
@Override
public void onBindViewHolder(CommonRecyclerViewHolder holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
Bundle bundle = (Bundle) payloads.get(0);
partialBindData(holder, bundle);
}
}
@Override
public void onBindViewHolder(CommonRecyclerViewHolder holder, int position) {
entirelyBindData(holder, dataList.get(position));
if (clickListener != null) {
holder.setClickListener(clickListener);
}
if (longClickListener != null) {
holder.setLongClickListener(longClickListener);
}
}
//省略一些代碼
......
private List<T> clone(List<T> dataList) {
List<T> tempDataList = new ArrayList<>(dataList.size());
for (T data : dataList) {
tempDataList.add(clone(data));
}
return tempDataList;
}
/**
* clone 指定對象暇屋,以此獲得對象副本
*
* @param data 要復(fù)制的對象
* @return 對象副本
*/
protected abstract T clone(T data);
/**
* 判斷數(shù)據(jù)列表刷新前后指定索引的位置是否指向同一條數(shù)據(jù)
* 此處只對比兩者是否指向同一條數(shù)據(jù)似袁,而不關(guān)心其數(shù)據(jù)內(nèi)容是否有變化
*
* @param oldItemPosition 更新前的數(shù)據(jù)索引
* @param newItemPosition 更新后的數(shù)據(jù)索引
* @return 是否指向同一條數(shù)據(jù)
*/
protected abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
/**
* 此處來判斷指向同一條數(shù)據(jù)的前后兩個(gè)索引位置,其數(shù)據(jù)內(nèi)容是否相同
* 只在 areItemsTheSame 返回 true 時(shí)才會(huì)調(diào)用本方法
*
* @param oldItemPosition 更新前的數(shù)據(jù)索引
* @param newItemPosition 更新后的數(shù)據(jù)索引
* @return 數(shù)據(jù)內(nèi)容是否有變化
*/
protected abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
/**
* 獲取同條數(shù)據(jù)在刷新前后是哪些數(shù)據(jù)內(nèi)容發(fā)生了變化
* 只在 areContentsTheSame 返回 false 時(shí)才會(huì)調(diào)用本方法
*
* @param oldItemPosition 更新前的數(shù)據(jù)索引
* @param newItemPosition 更新后的數(shù)據(jù)索引
* @return 數(shù)據(jù)變化內(nèi)容
*/
@NonNull
protected abstract Bundle getChangePayload(int oldItemPosition, int newItemPosition);
/**
* 對刷新前后的數(shù)據(jù)進(jìn)行定向更新咐刨,即只更新數(shù)據(jù)發(fā)生了變化的View
*
* @param holder Holder
* @param bundle getChangePayload 方法的返回值
*/
protected abstract void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle);
/**
* 對數(shù)據(jù)進(jìn)行完全綁定
*
* @param holder Holder
* @param data Data
*/
protected abstract void entirelyBindData(CommonRecyclerViewHolder holder, T data);
}
此處還需要使用到一個(gè)通用的 RecyclerView.ViewHolder
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:52
* 說明:通用RecyclerView ViewHolder
*/
public class CommonRecyclerViewHolder extends RecyclerView.ViewHolder {
public interface OnClickListener {
void onClick(int position);
}
public interface OnLongClickListener {
void onLongClick(int position);
}
private OnClickListener clickListener;
private OnLongClickListener longClickListener;
//用來存放View以減少findViewById的次數(shù)
private SparseArray<View> viewSparseArray;
CommonRecyclerViewHolder(View view) {
super(view);
viewSparseArray = new SparseArray<>();
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(getAdapterPosition());
}
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (longClickListener != null) {
longClickListener.onLongClick(getAdapterPosition());
}
return true;
}
});
}
void setClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
void setLongClickListener(OnLongClickListener longClickListener) {
this.longClickListener = longClickListener;
}
/**
* 根據(jù) ID 來獲取 View
*
* @param viewId viewID
* @param <T> 泛型
* @return 將結(jié)果強(qiáng)轉(zhuǎn)為 View 或 View 的子類型
*/
private <T extends View> T getView(@IdRes int viewId) {
// 先從緩存中找昙衅,找到的話則直接返回
// 如果找不到則findViewById,再把結(jié)果存入緩存中
View view = viewSparseArray.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
if (view != null) {
viewSparseArray.put(viewId, view);
}
}
return (T) view;
}
public CommonRecyclerViewHolder setText(@IdRes int viewId, CharSequence text) {
TextView textView = getView(viewId);
if (textView != null) {
textView.setText(text);
}
return this;
}
//省略一些代碼
......
}
二所宰、通用單布局 Adapter 使用示例
先來新建一個(gè)Model
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:55
* 說明:
*/
public class New {
private int index;
private String title;
private String content;
public New(int index, String title, String content) {
this.index = index;
this.title = title;
this.content = content;
}
//省略一些代碼
......
}
每個(gè)子項(xiàng)的布局包含一個(gè)索引TextView绒尊、一個(gè)標(biāo)題TextView、一個(gè)內(nèi)容TextView
<?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:background="#439af1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="6dp"
android:textSize="25sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="5dp"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:padding="10dp"
android:textSize="22sp" />
</LinearLayout>
之后就是繼承 CommonRecyclerViewAdapter 仔粥,泛型指定為 New ,在構(gòu)造函數(shù)里直接指定要使用的布局為 R.layout.item_new蟹但,再實(shí)現(xiàn)幾個(gè)抽象方法即可躯泰。
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:55
* 說明:單個(gè)布局
*/
public class NewCommonAdapter extends CommonRecyclerViewAdapter<New> {
public NewCommonAdapter(Context context, List<New> dataList) {
super(context, dataList, R.layout.item_new);
}
@Override
protected New clone(New data) {
return new New(data.getIndex(), data.getTitle(), data.getContent());
}
@Override
protected boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return dataList.get(oldItemPosition).getIndex() == newDataList.get(newItemPosition).getIndex();
}
@Override
protected boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
String title = dataList.get(oldItemPosition).getTitle();
String content = dataList.get(oldItemPosition).getContent();
String newTitle = newDataList.get(newItemPosition).getTitle();
String newContent = newDataList.get(newItemPosition).getContent();
return title.equals(newTitle) && content.equals(newContent);
}
@NonNull
@Override
protected Bundle getChangePayload(int oldItemPosition, int newItemPosition) {
Bundle bundle = new Bundle();
String title = dataList.get(oldItemPosition).getTitle();
String content = dataList.get(oldItemPosition).getContent();
String newTitle = newDataList.get(newItemPosition).getTitle();
String newContent = newDataList.get(newItemPosition).getContent();
if (!title.equals(newTitle)) {
bundle.putString("Title", newTitle);
}
if (!content.equals(newContent)) {
bundle.putString("Content", newContent);
}
return bundle;
}
@Override
protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) {
for (String key : bundle.keySet()) {
switch (key) {
case "Title":
holder.setText(R.id.tv_title, bundle.getString(key));
break;
case "Content":
holder.setText(R.id.tv_content, bundle.getString(key));
break;
}
}
}
@Override
protected void entirelyBindData(CommonRecyclerViewHolder holder, New data) {
holder.setText(R.id.tv_title, data.getTitle())
.setText(R.id.tv_content, data.getContent());
}
}
這里省略 Activity 中對 RecyclerView 的各種初始化操作,只展現(xiàn)最終效果华糖,具體代碼看最下方 GitHub 地址
三麦向、通用多布局 Adapter 使用示例
在 CommonRecyclerViewAdapter 類當(dāng)中,為了支持多布局客叉,通過 MultiTypeSupport 接口來返回相應(yīng)的布局文件ID诵竭,這里除了使用 單布局 中使用的 R.layout.item_new 布局文件外话告,同時(shí)使用另外一個(gè)布局文件 R.layout.item_new_multi ,僅僅是背景色不同而已
想要讓子 Adapter 支持多布局卵慰,只要實(shí)現(xiàn) CommonRecyclerViewAdapter 的另外一個(gè)構(gòu)造函數(shù)即可沙郭。這里在索引為單數(shù)時(shí)使用 R.layout.item_new 布局,雙數(shù)時(shí)使用 R.layout.item_new_multi 布局
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:56
* 說明:多布局
*/
public class NewCommonMultiAdapter extends CommonRecyclerViewAdapter<New> {
public NewCommonMultiAdapter(Context context, List<New> dataList) {
super(context, dataList, new CommonRecyclerViewAdapter.MultiTypeSupport<New>() {
@Override
public int getLayoutId(New item, int position) {
return item.getIndex() % 2 == 0 ? R.layout.item_new_multi : R.layout.item_new;
}
});
}
//省略一些代碼
......
@Override
protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) {
if (bundle.size() > 0) {
int index = bundle.getInt("Index");
for (String key : bundle.keySet()) {
switch (key) {
case "Title":
if (index % 2 == 0) {
holder.setText(R.id.tv_multi_title, bundle.getString(key));
} else {
holder.setText(R.id.tv_title, bundle.getString(key));
}
break;
case "Content":
if (index % 2 == 0) {
holder.setText(R.id.tv_multi_content, bundle.getString(key));
} else {
holder.setText(R.id.tv_content, bundle.getString(key));
}
break;
}
}
}
}
@Override
protected void entirelyBindData(CommonRecyclerViewHolder holder, New data) {
if (data.getIndex() % 2 == 0) {
holder.setText(R.id.tv_multi_title, data.getTitle())
.setText(R.id.tv_multi_content, data.getContent())
.setText(R.id.tv_multi_index, String.valueOf(data.getIndex()));
} else {
holder.setText(R.id.tv_title, data.getTitle())
.setText(R.id.tv_content, data.getContent())
.setText(R.id.tv_index, String.valueOf(data.getIndex()));
}
}
}
四裳朋、添加頭部與底部 View
為了實(shí)現(xiàn)帶頭部與底部View的 RecyclerView 病线,需要自定義一個(gè)Adapter來包裹實(shí)際的Adapter,重寫 getItemViewType(int position) 方法鲤嫡,返回不同的值以區(qū)分頭部View與底部View送挑,并向外開放添加和移除頭部底部View的方法
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:53
* 說明:可添加頭部View與底部View的RecyclerView Adapter
*/
public class WrapRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter<RecyclerView.ViewHolder> innerAdapter;
private SparseArray<View> headerViewArray;
private SparseArray<View> footerViewArray;
//頭部View類型開始位置,用于viewType
private static int BASE_VIEW_TYPE_HEADER = 1000;
//底部View類型開始位置,用于viewType
private static int BASE_VIEW_TYPE_FOOTER = 2000;
private RecyclerView.AdapterDataObserver dataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
notifyItemRangeChanged(positionStart + getHeaderViewCount(), itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
notifyItemRangeInserted(positionStart + getHeaderViewCount(), itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
notifyItemRangeRemoved(positionStart + getHeaderViewCount(), itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
int headerViewsCountCount = getHeaderViewCount();
notifyItemRangeChanged(fromPosition + headerViewsCountCount, toPosition + headerViewsCountCount + itemCount);
}
};
public WrapRecyclerViewAdapter(RecyclerView.Adapter innerAdapter) {
headerViewArray = new SparseArray<>();
footerViewArray = new SparseArray<>();
setAdapter(innerAdapter);
}
private void setAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
innerAdapter = adapter;
innerAdapter.registerAdapterDataObserver(dataObserver);
}
@Override
public int getItemViewType(int position) {
if (isHeaderPosition(position)) {
return headerViewArray.keyAt(position);
}
if (isFooterPosition(position)) {
return footerViewArray.keyAt(position - headerViewArray.size() - getDataItemCount());
}
return innerAdapter.getItemViewType(position - headerViewArray.size());
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (isHeaderView(viewType)) {
return new ViewHolder(headerViewArray.get(viewType));
}
if (isFooterView(viewType)) {
return new ViewHolder(footerViewArray.get(viewType));
}
return innerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int headerViewsCountCount = getHeaderViewCount();
if (position >= headerViewsCountCount && position < headerViewsCountCount + innerAdapter.getItemCount()) {
innerAdapter.onBindViewHolder(holder, position - headerViewsCountCount);
}
}
@Override
public int getItemCount() {
return getHeaderViewCount() + getDataItemCount() + getFooterViewCount();
}
private int getDataItemCount() {
return innerAdapter == null ? 0 : innerAdapter.getItemCount();
}
public int getHeaderViewCount() {
return headerViewArray.size();
}
public int getFooterViewCount() {
return footerViewArray.size();
}
public RecyclerView.Adapter getInnerAdapter() {
return innerAdapter;
}
/**
* 判斷是否是頭部View
*
* @param viewType ViewType
* @return 是否是頭部View
*/
private boolean isHeaderView(int viewType) {
return headerViewArray.indexOfKey(viewType) > -1;
}
/**
* 判斷是否是底部View
*
* @param viewType ViewType
* @return 是否是底部View
*/
private boolean isFooterView(int viewType) {
return footerViewArray.indexOfKey(viewType) > -1;
}
/**
* 判斷是否是頭部View
*
* @param view View
* @return 是否是頭部View
*/
public boolean isHeaderView(View view) {
return headerViewArray.indexOfValue(view) > -1;
}
/**
* 判斷是否是底部View
*
* @param view View
* @return 是否是底部View
*/
public boolean isFooterView(View view) {
return footerViewArray.indexOfValue(view) > -1;
}
/**
* 根據(jù)索引判斷該位置的View是否是頭部View
*
* @param position 索引
* @return 是否是頭部View
*/
private boolean isHeaderPosition(int position) {
return position >= 0 && position < getHeaderViewCount();
}
/**
* 根據(jù)索引判斷該位置的View是否是底部View
*
* @param position 索引
* @return 是否是底部View
*/
private boolean isFooterPosition(int position) {
return position >= (getHeaderViewCount() + getDataItemCount())
&& position < (getHeaderViewCount() + getDataItemCount() + getFooterViewCount());
}
/**
* 添加頭部View
*
* @param view 頭部View
*/
public void addHeaderView(View view) {
if (headerViewArray.indexOfValue(view) < 0) {
headerViewArray.put(BASE_VIEW_TYPE_HEADER++, view);
notifyItemInserted(headerViewArray.size() - 1);
}
}
/**
* 添加底部View
*
* @param view 底部View
*/
public void addFooterView(View view) {
if (footerViewArray.indexOfValue(view) < 0) {
footerViewArray.put(BASE_VIEW_TYPE_FOOTER++, view);
notifyItemInserted(getHeaderViewCount() + getDataItemCount() + getFooterViewCount() - 1);
}
}
/**
* 移除頭部View
*
* @param view View
*/
public void removeHeaderView(View view) {
int index = headerViewArray.indexOfValue(view);
if (index > -1) {
headerViewArray.removeAt(index);
notifyItemRemoved(index);
}
}
/**
* 移除底部View
*
* @param view View
*/
public void removeFooterView(View view) {
int index = footerViewArray.indexOfValue(view);
if (index > -1) {
footerViewArray.removeAt(index);
notifyItemRemoved(getHeaderViewCount() + getDataItemCount() + index);
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
ViewHolder(View itemView) {
super(itemView);
}
}
}
WrapRecyclerViewAdapter 的使用方法也很簡單,只要將實(shí)際的Adapter作為參數(shù)來構(gòu)造 WrapRecyclerViewAdapter 對象暖眼,然后再傳給 RecyclerView 即可惕耕,之后就可以通過 addHeaderView 和 addFooterView 方法來添加頭部和底部View
RecyclerView rv_wrapDataList = (RecyclerView) findViewById(R.id.rv_wrapDataList);
NewCommonAdapter adapter = new NewCommonAdapter(this, newList);
WrapRecyclerViewAdapter wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(adapter);
rv_wrapDataList.setLayoutManager(new LinearLayoutManager(this));
rv_wrapDataList.setAdapter(wrapRecyclerViewAdapter);
五、單擊和長按事件監(jiān)聽
RecyclerView 的單擊和長按事件一直是一個(gè)比較麻煩的地方诫肠,畢竟沒有官方提供的接口赡突,不過此處 CommonRecyclerViewAdapter 也已經(jīng)提供了相應(yīng)的設(shè)置方法
NewCommonMultiAdapter newCommonMultiAdapter = new NewCommonMultiAdapter(this, newList);
newCommonMultiAdapter.setClickListener(new CommonRecyclerViewHolder.OnClickListener() {
@Override
public void onClick(int position) {
toast("單擊" + "\n" + newList.get(position).getTitle() + "\n" + newList.get(position).getContent());
}
});
newCommonMultiAdapter.setLongClickListener(new CommonRecyclerViewHolder.OnLongClickListener() {
@Override
public void onLongClick(int position) {
toast("長按" + "\n" + newList.get(position).getTitle() + "\n" + newList.get(position).getContent());
}
});
六、通用 RecyclerView.ItemDecoration
想要實(shí)現(xiàn)Item之間的分割線区赵,需要繼承 RecyclerView.ItemDecoration 在相應(yīng)的位置進(jìn)行繪制惭缰,這里提供一個(gè)通用的分隔線
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 21:52
* 說明:通用分隔線
*/
public class CommonItemDecoration extends RecyclerView.ItemDecoration{
private int orientation = LinearLayoutManager.HORIZONTAL;
private Drawable drawable;
public CommonItemDecoration(Context context, int orientation) {
this.orientation = orientation;
int[] attrs = new int[]{android.R.attr.listDivider};
TypedArray typedArray = context.obtainStyledAttributes(attrs);
drawable = typedArray.getDrawable(0);
typedArray.recycle();
}
public CommonItemDecoration(Drawable drawable, int orientation) {
this.drawable = drawable;
this.orientation = orientation;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (orientation == LinearLayoutManager.HORIZONTAL) {
outRect.set(0, 0, drawable.getIntrinsicWidth(), 0);
} else if (orientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, 0, drawable.getIntrinsicHeight());
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (orientation == LinearLayoutManager.HORIZONTAL) {
drawVerticalDivider(c, parent);
} else if (orientation == LinearLayoutManager.VERTICAL) {
drawHorizontalDivider(c, parent);
}
}
private void drawVerticalDivider(Canvas c, RecyclerView parent) {
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
// RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
// //受 child layout_marginEnd 屬性的影響
// int left = child.getRight() + params.rightMargin;
//不受 child layout_marginEnd 屬性的影響,會(huì)直接繪制在 child 右側(cè)
int left = child.getRight();
int top = child.getTop();
int right = left + drawable.getIntrinsicWidth();
int bottom = child.getBottom();
drawable.setBounds(left, top, right, bottom);
drawable.draw(c);
}
}
private void drawHorizontalDivider(Canvas c, RecyclerView parent) {
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
int left = child.getLeft();
//不受 child layout_marginBottom 屬性的影響笼才,會(huì)直接繪制在 child 底部
int top = child.getBottom();
int right = child.getRight();
int bottom = top + drawable.getIntrinsicHeight();
//會(huì)受 child layout_marginBottom 屬性的影響
//RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
//int top = child.getBottom() + params.bottomMargin;
drawable.setBounds(left, top, right, bottom);
drawable.draw(c);
}
}
}
在之前的幾張效果圖中漱受,其實(shí)是已經(jīng)為 RecyclerView 添加了一條白色分隔線的
RecyclerView rv_commonMultiDataList = (RecyclerView) findViewById(R.id.rv_commonMultiDataList);
CommonItemDecoration commonItemDecoration = new CommonItemDecoration(ContextCompat.getDrawable(this, R.drawable.divider), LinearLayoutManager.VERTICAL);
rv_commonMultiDataList.addItemDecoration(commonItemDecoration);
這里也可以傳入不同的 Drawable 對象,從而實(shí)現(xiàn)多種風(fēng)格的分隔線
七骡送、SnapHelper的使用
順帶在這里對 SnapHelper 進(jìn)行一個(gè)簡單的介紹昂羡,SnapHelper 是在 Android 24.2.0 的support 包中新添加的一個(gè)支持庫,是對RecyclerView的拓展摔踱。SnapHelper旨在支持RecyclerView的對齊方式虐先,通過計(jì)算對齊RecyclerView中TargetView 的指定點(diǎn)或者容器中的任何像素點(diǎn),可以使RecyclerView實(shí)現(xiàn)類似于 ViewPager 的切換效果
SnapHelper是一個(gè)抽象類派敷,官方提供了 LinearSnapHelper 和 PagerSnapHelper 兩個(gè)具體實(shí)現(xiàn)蛹批,這里來實(shí)現(xiàn)類似于第一次使用App時(shí)顯示的引導(dǎo)頁的效果
/**
* 作者:葉應(yīng)是葉
* 時(shí)間:2017/12/21 22:02
* 說明:
*/
public class SnapRecyclerViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_snap_recycler_view);
RecyclerView rv_snap = (RecyclerView) findViewById(R.id.rv_snap);
SnapAdapter snapAdapter = new SnapAdapter(this, getData());
rv_snap.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
// rv_snap.setLayoutManager(new LinearLayoutManager(this));
rv_snap.setAdapter(snapAdapter);
// LinearSnapHelper snapHelper = new LinearSnapHelper();
// snapHelper.attachToRecyclerView(rv_snap);
PagerSnapHelper pagerSnapHelper=new PagerSnapHelper();
pagerSnapHelper.attachToRecyclerView(rv_snap);
}
private List<Image> getData() {
List<Image> imageList = new ArrayList<>();
imageList.add(new Image(0, R.drawable.drawable_1));
imageList.add(new Image(1, R.drawable.drawable_0));
imageList.add(new Image(2, R.drawable.drawable_1));
imageList.add(new Image(3, R.drawable.drawable_0));
imageList.add(new Image(4, R.drawable.drawable_1));
imageList.add(new Image(5, R.drawable.drawable_0));
imageList.add(new Image(6, R.drawable.drawable_1));
imageList.add(new Image(7, R.drawable.drawable_0));
return imageList;
}
}
源代碼我也已經(jīng)放到了GitHub上,點(diǎn)擊查看:Android RecyclerView 的簡便寫法