想想還是有必要上一波官話的:
-
RecyclerView
是support:recyclerview-v7
中提供的控件,最低兼容到android 3.0
版本痢虹。 -
RecyclerView
是ListView
的更高級和靈活的版本武氓。 - 提供了
LinearLayoutManager
(類listview
)梯皿、GridLayoutManager
(類gridview
)、StaggeredGridLayoutManager
(瀑布流)三種排列方式县恕。
簡單來說东羹,RecyclerView
是控件里的C位也不為過了。
一忠烛、Why And What?
RecyclerView
是我平時開發(fā)工作中可以說是最常用的組件了属提,所以做適當(dāng)并且適合的封裝是必須的。
目前關(guān)于RecyclerView
的封裝可以說不要太多哦美尸,為什么我還要選擇自己封裝呢冤议?
其實在做這個封裝之前我也瀏覽過很多大神對于RecyclerView
的封裝,但是普遍存在兩個基本的問題:
- 只針對某一方面做封裝
- 封裝內(nèi)容過多且復(fù)雜师坎,很多都是我用不上的
我自己對于RecyclerView
的需求有哪些
1. 萬用型的ViewHolder
恕酸,不用再每個界面寫ViewHolder
。
2. 繼承自RecyclerView.Adapter
的基類Adapter
胯陋,負(fù)責(zé)管理RecyclerView
的數(shù)據(jù)蕊温。
3. 下拉刷新和上拉加載(這個麻煩的地方在于要有一定的特效,雖然很多封裝都實現(xiàn)了惶岭,但是大多簡陋寿弱,優(yōu)美的少,很影響用戶體驗)按灶。
4. 多種布局的實現(xiàn)症革,即列表中存在不同的布局。
5. 簡潔地實現(xiàn)item中數(shù)據(jù)和view的綁定鸯旁。
6. 將一個RecyclerView
置于一個BaseFragment
中噪矛,通過BaseFragment
來實現(xiàn)一個最簡單RecyclerView
的頁面。
二铺罢、ViewHolder和RecyclerView.Adapter
public class ViewHolderRocket extends RecyclerView.ViewHolder {
private SparseArray<View> views;
private View mItemView;
public ViewHolderRocket(View itemView) {
super(itemView);
views = new SparseArray<>();
mItemView = itemView;
}
public View getmItemView() {
return mItemView;
}
public View getView(int resId) {
return retrieveView(resId);
}
protected <V extends View> V retrieveView(int viewId){
View view = views.get(viewId);
if(view == null){
view = mItemView.findViewById(viewId);
views.put(viewId,view);
}
return (V) view;
}
public TextView getTextView(int resId){
return retrieveView(resId);
}
public ImageView getImageView(int resId){
return retrieveView(resId);
}
public Button getButton(int resId){
return retrieveView(resId);
}
public LinearLayout getLinearLayout(int resId){return retrieveView(resId);}
public void setText(int resId,CharSequence text){
getTextView(resId).setText(text);
}
public void setText(int resId,int strId){
getTextView(resId).setText(strId);
}
}
ViewHolderRocket
繼承自RecyclerView.ViewHolder
艇挨,將item
的view
存儲在SparseArray
數(shù)組中,避免了重復(fù)的findViewById
韭赘,提高了效率和性能缩滨。
關(guān)于BaseAdapter,最主要的作用就是對于數(shù)據(jù)的操作,包括初始設(shè)置脉漏、新增苞冯、刪除和清除所有。
一下代碼展示了除了這些之外的另一個重要作用侧巨,那就是用于不同布局的實現(xiàn):
public class RVBaseAdapter<C extends RVCell> extends RecyclerView.Adapter<ViewHolderRocket> {
private static final String TAG = "RVBaseAdapter";
protected List<C> mData;
public RVBaseAdapter() {
mData = new ArrayList<>();
}
public List<C> getData() {
return mData;
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
@Override
public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolderRocket holder, int position) {
mData.get(position).bindViewHolder(holder, position);
}
@Override
public void onViewDetachedFromWindow(ViewHolderRocket holder) {
super.onViewDetachedFromWindow(holder);
Log.e(TAG, "onViewDetachedFromWindow invoke...");
//釋放資源
int position = holder.getAdapterPosition();
//越界檢查
if (position < 0 || position >= mData.size()) {
return;
}
mData.get(position).releaseResource();
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
/**
* add one cell
*
* @param cell
*/
public void add(C cell) {
mData.add(cell);
int index = mData.indexOf(cell);
notifyItemInserted(index);
}
public void add(int index, C cell) {
mData.add(index, cell);
notifyItemInserted(index);
}
/**
* remove a cell
*
* @param cell
*/
public void remove(C cell) {
int indexOfCell = mData.indexOf(cell);
remove(indexOfCell);
}
public void remove(int index) {
mData.remove(index);
notifyItemRemoved(index);
}
/**
* @param start
* @param count
*/
public void remove(int start, int count) {
if ((start + count) > mData.size()) {
return;
}
mData.subList(start, start + count).clear();
notifyItemRangeRemoved(start, count);
}
/**
* add a cell list
*
* @param cells
*/
public void addAll(List<C> cells) {
if (cells == null || cells.size() == 0) {
return;
}
Log.e(TAG, "addAll cell size:" + cells.size());
mData.addAll(cells);
notifyItemRangeInserted(mData.size() - cells.size(), mData.size());
}
public void addAll(int index, List<C> cells) {
if (cells == null || cells.size() == 0) {
return;
}
mData.addAll(index, cells);
notifyItemRangeInserted(index, index + cells.size());
}
public void clear() {
mData.clear();
notifyDataSetChanged();
}
}
關(guān)于不同布局的實現(xiàn)舅锄,我們留到后面再講。
三司忱、下拉刷新和上拉加載
想要實現(xiàn)下拉刷新和上拉加載皇忿,說簡單也簡單,Google
官方自己提供了SwipeRefreshLayout
控件來幫助我們實現(xiàn)下拉刷新坦仍,上拉加載也可以通過監(jiān)控列表中最后一個數(shù)據(jù)的位置來判斷是否要加載鳍烁。
但是效果也就是一般般,對于已經(jīng)習(xí)慣了微博桨踪、微信老翘、支付寶那些強(qiáng)大的刷新加載功能的用戶來說芹啥,他們當(dāng)然不會滿足于這種最原始的方式锻离。
想要做的酷炫,就要做出炫酷的動畫特效墓怀,自己做的話實在是力不從心啊汽纠,沒有扎實的view基礎(chǔ),真的望洋興嘆的感覺傀履。
還有有大神幫我們解決了這個問題:
強(qiáng)大且易用的SmartRefreshLayout
在RvFragment
中有可以設(shè)置Header和Footer的方法虱朵,以及設(shè)置是否支持下拉刷新和上拉加載的方法:
/**
* 是否啟用下拉刷新功能
*
* @param enable
*/
protected void setEnableRefresh(boolean enable) {
refreshLayout.setEnableRefresh(enable);
}
/**
* 是否啟用上拉加載功能
*
* @param enable
*/
protected void setEnableLoadMore(boolean enable) {
refreshLayout.setEnableLoadMore(enable);
}
/**
* 設(shè)置下拉刷新Header
*
* @param header
*/
protected void setRefreshHeader(RefreshHeader header) {
refreshLayout.setRefreshHeader(header);
}
/**
* 設(shè)置上拉加載Footer
* @param footer
*/
protected void setRefreshFooter(RefreshFooter footer) {
refreshLayout.setRefreshFooter(footer);
}
感興趣的同學(xué)可以看看,有很強(qiáng)大的Header
和Footer
樣式钓账。
四碴犬、多種布局的實現(xiàn)
這本來是產(chǎn)品經(jīng)理提出的需求
- 用在數(shù)據(jù)頁面,能夠在列表里面根據(jù)某些特殊的數(shù)據(jù)來展示不同的頁面
- 用在首頁梆暮,一個
RecyclerView
就展示全復(fù)雜的主頁
public interface Cell {
/**
* 回收資源
*/
void releaseResource();
/**
* 獲取viewType
*
* @return
*/
int getItemType();
/**
* 綁定ViewHolder
*
* @param holder
* @param position
* @return
*/
void bindViewHolder(RVBaseViewHolder holder, int position);
}
public abstract class RVCell<T> implements Cell {
public T mData;
public RVCell(T t) {
mData = t;
}
}
實現(xiàn):
public class TestCell extends RVCell<PersonBean> {
public TestCell(PersonBean bean) {
super(bean);
}
@Override
public void releaseResource() {
}
@Override
public int getItemType() {
return R.layout.rv_item;
}
@Override
public void bindViewHolder(final ViewHolderRocket holder, final int position) {
holder.setText(R.id.store_name_tv, mData.getName());
holder.setText(R.id.store_address_tv, mData.getAge());
holder.setText(R.id.store_owner_tv, mData.getPhone());
final View mItemView = holder.getmItemView();
mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText( mItemView.getContext(), position + "", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(mItemView.getContext(), MainActivity.class);
mItemView.getContext().startActivity(intent);
}
});
}
}
這時候我們再回頭看上面的RVBaseAdapter
RVBaseAdapter<C extends RVBaseCell> extends RecyclerView.Adapter<RVBaseViewHolder>{
protected List<C> mData;
public RVBaseAdapter() {
mData = new ArrayList<>();
}
}
RVBaseCell
持有數(shù)據(jù)mData
和視圖holder
服协,即一個Item
。
接下來我們看RVAdapter
的三個重要方法:
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
@Override
public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolderRocket holder, int position) {
mData.get(position).bindViewHolder(holder, position);
}
-
Cell
的getItemType()
方法返回的是item
的布局layout
啦粹。 - 在
RVAdapter
的getItemViewType
方法偿荷,return
“布局的id
”。 -
onCreateViewHolder(ViewGroup parent, int viewType)
中的viewType
即是getItemViewType
方法返回的布局id
唠椭,根據(jù)這個id獲取視圖跳纳,并返回ViewHolderRocket
。 - 在
onBindViewHolder
中調(diào)用mData
的bindViewHolder
方法贪嫂,然后在TestCell
(實際的實現(xiàn)方式)實現(xiàn)該方法寺庄。 - 在
mData
的bindViewHolder
方法中實現(xiàn)item
中數(shù)據(jù)和view
的綁定。
五、基于RecyclerView
的RvBaseFragment
public abstract class RvFragment<T> extends Fragment {
public static final String TAG = "RvFragment";
private FrameLayout titleLayout;
protected RecyclerView mRecyclerView;
protected RVAdapter mBaseAdapter;
protected SmartRefreshLayout refreshLayout;
protected int pageNumber = 1;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.rv_base_fragment_layout, null);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
initRefreshListener();
initRecycleView();
onRecyclerViewInitialized();
}
private void initView(View view) {
titleLayout = view.findViewById(R.id.title_layout);
refreshLayout = view.findViewById(R.id.refreshLayout);
mRecyclerView = view.findViewById(R.id.base_fragment_rv);
mRecyclerView.setLayoutManager(initLayoutManger());
}
private void initRefreshListener() {
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshlayout) { //下拉刷新
refreshlayout.finishRefresh(500);
pageNumber = 1;
RvFragment.this.onRefresh();
}
});
refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshlayout) { //上拉加載
refreshlayout.finishLoadMore(500);
pageNumber++;
RvFragment.this.onLoadMore();
}
});
refreshLayout.setRefreshHeader(new ClassicsHeader(this.getActivity()).setPrimaryColor(colorUtil.getColorPrimary(this.getActivity())));
refreshLayout.setRefreshFooter(new ClassicsFooter(this.getActivity()).setPrimaryColor(getResources().getColor(R.color.material_gray_300)));
}
/**
* 設(shè)置下拉刷新Header
*
* @param header
*/
protected void setRefreshHeader(RefreshHeader header) {
refreshLayout.setRefreshHeader(header);
}
/**
* 設(shè)置上拉加載Footer
* @param footer
*/
protected void setRefreshFooter(RefreshFooter footer) {
refreshLayout.setRefreshFooter(footer);
}
private void initRecycleView() {
mBaseAdapter = initAdapter();
mRecyclerView.setAdapter(mBaseAdapter);
}
/**
* 設(shè)置頭布局斗塘,不在列表中
*
* @param view
*/
protected void addTitleView(View view) {
if (view == null) {
return;
}
titleLayout.addView(view);
}
/**
* 子類可以自己指定Adapter,如果不指定默認(rèn)RVSimpleAdapter
*
* @return
*/
protected RVAdapter initAdapter() {
return new RVAdapter();
}
/**
* 子類自己指定RecyclerView的LayoutManager,如果不指定饶深,默認(rèn)為LinearLayoutManager,VERTICAL 方向
*
* @return
*/
protected RecyclerView.LayoutManager initLayoutManger() {
LinearLayoutManager manager = new LinearLayoutManager(getContext());
manager.setOrientation(LinearLayoutManager.VERTICAL);
return manager;
}
/**
* 是否啟用下拉刷新功能
*
* @param enable
*/
protected void setEnableRefresh(boolean enable) {
refreshLayout.setEnableRefresh(enable);
}
/**
* 是否啟用上拉加載功能
*
* @param enable
*/
protected void setEnableLoadMore(boolean enable) {
refreshLayout.setEnableLoadMore(enable);
}
/**
* RecyclerView 初始化完畢,可以在這個方法里綁定數(shù)據(jù)
*/
public abstract void onRecyclerViewInitialized();
/**
* 下拉刷新
*/
public abstract void onRefresh();
/**
* 上拉加載更多
*/
public abstract void onLoadMore();
/**
* 根據(jù)實體生成對應(yīng)的Cell
*
* @param list 實體列表
* @return cell列表
*/
protected abstract List<Cell> getCells(List<T> list);
}
實際使用:
public class RecyclerViewFragment extends RvFragment {
public static RecyclerViewFragment newInstance() {
RecyclerViewFragment fragment = new RecyclerViewFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onRecyclerViewInitialized() {
mBaseAdapter.showLoading();
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.removeLoading();
mBaseAdapter.addAll(getCells(dataList));
}
@Override
public void onRefresh() {
mBaseAdapter.clear();
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.addAll(getCells(dataList));
Toast.makeText(this.getActivity(), "更新啦", Toast.LENGTH_SHORT).show();
}
@Override
public void onLoadMore() {
ArrayList<PersonBean> dataList = new ArrayList<>();
for (int i = 10 * (pageNumber - 1); i < pageNumber * 10; i++) {
PersonBean bean = new PersonBean();
bean.setName("姓名" + i + "");
bean.setAge("20");
bean.setPhone("18956321458");
dataList.add(bean);
}
mBaseAdapter.addAll(getCells(dataList));
}
@Override
protected List<Cell> getCells(List list) {
List<Cell> cells = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
PersonBean bean = (PersonBean) list.get(i);
cells.add(new TestCell(bean));
}
return cells;
}
}