AndroidRecyclerView從入門到玩壞
目錄
- 前言
- 基礎使用
- 分隔線
- 點選監聽
- 搭配CardView
- 更豐富的條目
- 增刪條目
- 快速新增檢視
- 讓RecyclerView支援複雜檢視
- 最後
前言
RecyclerView在Android介面開發當中是很重要的, 那掌握它也是很必要的. 但是有些時候會覺得它很厚重, 這裡就從RecyclerView的基礎一直說到擴充套件, 讓你把RecyclerView學薄了.
RecyclerView官方文件也是非常厚重.
這篇文章融合了自己原來的多篇文章, 並進行了修正和改進, 而且新增了很多很有趣的內容.
本文需要20分鐘以上的閱讀時間, 請合理安排.
多圖預警, 轉載請註明出處!
基礎使用
要使用RecyclerView在Android Studio 2.x(以下簡稱AS), 要這樣:
compile `com.android.support:cardview-v7:25.3.1`
compile `com.android.support:recyclerview-v7:25.3.1`
到了AS 3.x, 要這樣:
implementation `com.android.support:cardview-v7:26.1.0`
implementation `com.android.support:recyclerview-v7:26.1.0`
之後在佈局檔案中寫入如下程式碼就引入了RecyclerView了.
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
接下來說說介紹些各種佈局. 可以看RecyclerView.LayoutManager官方文件.
佈局類 | 效果 |
---|---|
LinearLayoutManager | 以垂直或水平滾動列表方式顯示專案 |
GridLayoutManager | 在網格中顯示專案 |
StaggeredGridLayoutManager | 在分散對齊網格中顯示專案 |
mRvMain = (RecyclerView) findViewById(R.id.rv_main);
// 設定佈局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRvMain.setLayoutManager(linearLayoutManager);
最關鍵的還是介面卡的撰寫. 但是理解起來不是很難, 你只要將ListView的介面卡寫法帶入理解就好. 這裡把全部程式碼貼出來, 因為後面要在這個基礎上不斷擴充.
public class MyRVAdapter2 extends RecyclerView.Adapter<MyRVAdapter2.MyTVHolder> {
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private final ArrayList<String> mData;
public MyRVAdapter2(Context context) {
mLayoutInflater = LayoutInflater.from(context);
mContext = context;
mData = new ArrayList<>();
for (int i = 0; i < 40; i++) {
mData.add("hello " + i);
}
}
@Override
public MyRVAdapter2.MyTVHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false));
}
@Override
public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) {
holder.mTextView.setText(mData.get(pos));
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
class MyTVHolder extends RecyclerView.ViewHolder {
TextView mTextView;
MyTVHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_txt);
}
}
}
然後寫個最基礎的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:orientation="horizontal">
<TextView
android:id="@+id/tv_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="@dimen/eight_dp"
android:text="@string/tmp"
android:textSize="@dimen/thirty_sp" />
</LinearLayout>
分隔線
前面的部分已經是基礎的RecyclerView使用了. 那比起ListView是不是沒有了分隔線. 這裡上一個簡單好用的開源庫RecyclerView-FlexibleDivider.
引入:
implementation `com.yqritc:recyclerview-flexibledivider:1.4.0`
使用:
mRvMain.addItemDecoration(
new HorizontalDividerItemDecoration.Builder(this).build());
看效果就達到了吧.
覺得不好看, 還可以自定義, 更多寫法可以參見文件內容.
mRvMain.addItemDecoration(
new HorizontalDividerItemDecoration.Builder(this)
.color(Color.BLUE)
.sizeResId(R.dimen.two_dp)
.marginResId(R.dimen.eight_dp, R.dimen.eight_dp)
.build());
而且而且, 豎著的分隔線也大丈夫哦.
GridLayoutManager gridLayoutManager
= new GridLayoutManager(this, 2);
mRvMain.setLayoutManager(gridLayoutManager);
mRvMain.addItemDecoration(
new VerticalDividerItemDecoration.Builder(this).build());
點選監聽
再回憶一下在天國的ListView, 還有item的點選吧, 這個也要自己寫.
介面卡中:
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private MyRVAdapter2.OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(MyRVAdapter2.OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
onBindViewHolder中設定點選監聽.
@Override
public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) {
holder.mTextView.setText(mData.get(pos));
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(holder.itemView, pos);
return false;
}
});
}
}
使用監聽:
mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(UIUtil.getContext(), "click" + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(UIUtil.getContext(), "long click" + position, Toast.LENGTH_SHORT).show();
}
});
搭配CardView
是不是這個點選看著沒啥感覺, 沒事, 我們換上CardView再來一次.
佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/eight_dp"
android:foreground="@drawable/card_foreground"
card_view:cardCornerRadius="@dimen/four_dp">
<TextView
android:id="@+id/tv_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="@dimen/eight_dp"
android:text="@string/tmp"
android:textSize="@dimen/thirty_sp" />
</android.support.v7.widget.CardView>
給CardView加上水波紋點選特效:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorPrimary" />
在老版本就只能用選擇器了, 其實效果也還好:
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/card_foreground_selector"
android:insetBottom="@dimen/four_dp"
android:insetLeft="@dimen/three_dp"
android:insetRight="@dimen/three_dp"
android:insetTop="@dimen/four_dp" />
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="@color/colorPrimaryTran" />
<corners android:radius="@dimen/four_dp" />
</shape>
</item>
<item android:state_enabled="true" android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="#0f000000" />
<corners android:radius="@dimen/four_dp" />
</shape>
</item>
</selector>
更豐富的條目
大家應該都知道TextView可以設定圖示吧, 這裡來看下效果圖, 順帶感受下android介面設計語言的變化.
<?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:orientation="horizontal">
<TextView
android:id="@+id/tv_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableLeft="@mipmap/ic_launcher"
android:drawablePadding="@dimen/sixteen_dp"
android:drawableStart="@mipmap/ic_launcher"
android:gravity="center_vertical"
android:padding="@dimen/eight_dp"
android:text="@string/tmp"
android:textSize="@dimen/thirty_sp" />
</LinearLayout>
讓GridLayoutManager展示不同寬度的條目
方的是4.x上的, 圓的是8.x上的, 可以看到, 變化還是很大的. 我們回正題. GridLayoutManager佈局是可以設定寬度的, 不一定都是一樣大的, 來看下實現.
// 指定item寬度
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0
|| position == (mAdapter.getItemCount() - 1) / 2
|| position == (mAdapter.getItemCount() - 1)) {
return gridLayoutManager.getSpanCount();
} else {
return 1;
}
}
});
來看效果圖, 發現我們的分隔線崩了是吧, 如果真想用這個分隔線也還是要自己動手修補修補, 改動改動, 開源庫再棒也猜不到你的專案需求呀.
當然了, 我還是很喜歡這個分隔線的, 我們來看看橫著滾動的效果.
佈局檔案要改動:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_txt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/tmp"
android:textSize="@dimen/thirty_sp" />
</LinearLayout>
gridLayoutManager.setOrientation(GridLayoutManager.HORIZONTAL);
展示不同佈局
之前變化寬度其實還是相同條目, 現在要展示不同條目:
寫一個圖的條目:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/eight_dp">
<ImageView
android:id="@+id/iv_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/ic_launcher" />
</RelativeLayout>
public enum ITEM_TYPE {
ITEM_TYPE_IMAGE,
ITEM_TYPE_TEXT
}
這裡多了判斷條目型別, 還要注意返回值的變化, 用了更基類的RecyclerView.ViewHolder.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
return new MyRVAdapter2.MyIVHolder(mLayoutInflater.inflate(R.layout.rv_img_item, parent, false));
} else {
return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false));
}
}
類繼承上面也要變成RecyclerView.ViewHolder, 這些都是要對應的.
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
當然了, holder也是不能少的.
public class MyIVHolder extends RecyclerView.ViewHolder {
ImageView mImageView;
MyIVHolder(View view) {
super(view);
mImageView = (ImageView) view.findViewById(R.id.iv_img);
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int pos) {
if (holder instanceof MyRVAdapter2.MyTVHolder) {
((MyRVAdapter2.MyTVHolder) holder).mTextView.setText(mData.get(pos));
} else if (holder instanceof MyRVAdapter2.MyIVHolder) {
((MyRVAdapter2.MyIVHolder) holder).mImageView.setImageDrawable(UIUtil.getDrawable(R.mipmap.ic_launcher));
}
// 點選監聽
...
}
順帶的, 我們把之前放寬的條目變成不同的檢視, 也就是對應起來:
@Override
public int getItemViewType(int position) {
if (position == 0
|| position == (getItemCount() - 1) / 2
|| position == (getItemCount() - 1)) {
return ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal();
} else {
return ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
}
}
看看效果:
它還能繼續地複雜, 試試瀑布流StaggeredGridLayoutManager:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/eight_dp"
card_view:cardCornerRadius="@dimen/four_dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/tmp"
android:textSize="@dimen/thirty_sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
StaggeredGridLayoutManager staggeredGridLayoutManager
= new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
mRvMain.setLayoutManager(staggeredGridLayoutManager);
分割線又崩了, 嘿嘿, 其實用上了CardView, 分割線沒什麼必要再用了.
增刪條目
現在介面卡中新增增刪方法:
public void addData(int position) {
mData.add(position, "hello x");
notifyItemInserted(position);
}
public void removeData(int position) {
mData.remove(position);
notifyItemRemoved(position);
}
再寫入點選事件中, 點選增加, 長按刪除:
mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
mAdapter.addData(position);
}
@Override
public void onItemLongClick(View view, int position) {
mAdapter.removeData(position);
}
});
增刪條目開源庫
這裡再上一個開源庫recyclerview-animators, 可以修改增刪動畫, 種類也很豐富, 還能在它基礎上自定義:
分類 | 動畫類名 |
---|---|
Cool | LandingAnimator |
Scale | ScaleInAnimator, ScaleInTopAnimator, ScaleInBottomAnimator, ScaleInLeftAnimator, ScaleInRightAnimator |
Fade | FadeInAnimator, FadeInDownAnimator, FadeInUpAnimator, FadeInLeftAnimator, FadeInRightAnimator |
Flip | FlipInTopXAnimator, FlipInBottomXAnimator, FlipInLeftYAnimator, FlipInRightYAnimator |
Slide | SlideInLeftAnimator, SlideInRightAnimator, OvershootInLeftAnimator, OvershootInRightAnimator, SlideInUpAnimator, SlideInDownAnimator |
引入:
implementation `jp.wasabeef:recyclerview-animators:2.3.0`
使用:
mRvMain.setItemAnimator(new SlideInLeftAnimator());
這裡給大家展示兩種效果, 其它的自己嘗試吧.
mRvMain.setItemAnimator(new LandingAnimator());
快速新增檢視
還有像Header, Foot這樣的檢視, 自己寫也還是要費些功夫的, 這裡推薦Android大神的庫baseAdapter
引入:
implementation `com.zhy:base-rvadapter:3.0.3`
新增頭尾檢視
HeaderAndFooterWrapper mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter);
TextView t1 = new TextView(this);
t1.setText("Header 1");
t1.setTextSize(30);
TextView t2 = new TextView(this);
t2.setText("Foot 1");
t2.setTextSize(30);
mHeaderAndFooterWrapper.addHeaderView(t1);
mHeaderAndFooterWrapper.addFootView(t2);
mRvMain.setAdapter(mHeaderAndFooterWrapper);
新增更多檢視
LoadMoreWrapper mLoadMoreWrapper = new LoadMoreWrapper(mAdapter);
mLoadMoreWrapper.setLoadMoreView(R.layout.rv_cv_img_txt_item);
mLoadMoreWrapper.setOnLoadMoreListener(new LoadMoreWrapper.OnLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
}
});
mRvMain.setAdapter(mLoadMoreWrapper);
是不是感覺特別爽, 那看看更爽的, 在不寫介面卡的情況下快速新增條目:
final ArrayList<String> mData = new ArrayList<>();
for (int i = 0; i < 40; i++) {
mData.add("hello " + i);
}
mRvMain.setAdapter(new CommonAdapter<String>(this, R.layout.rv_cv_txt_item, mData) {
@Override
protected void convert(ViewHolder holder, String s, int position) {
holder.setText(R.id.tv_txt, mData.get(position));
}
});
是不是感覺省了一萬個小時呢.
讓RecyclerView支援複雜檢視
每次加入新的檢視都要對介面卡進行比較大程度的改動, 這樣是很容易出錯的. 這裡引入一個非常棒的開源庫-AdapterDelegates, 降低下程式碼耦合性.
引入:
implementation `com.hannesdorfmann:adapterdelegates3:3.0.1`
先不說使用細節, 來看看實現後想加入不同檢視有多簡單吧:
ArrayList<Base> data = new ArrayList<>();
for (int i = 0; i < 10; i++) {
data.add(new B("b " + i));
}
for (int i = 0; i < 10; i++) {
data.add(new A("a " + i));
}
BaseAdapter animalAdapter = new BaseAdapter(this, data);
mRvMain.setAdapter(animalAdapter);
是不是驚了, 也就是說, 你只要實現了A, B這些檢視類, 直接新建放入陣列就完事了.
需要寫基礎介面卡:
public class BaseAdapter extends RecyclerView.Adapter {
private AdapterDelegatesManager<List<Base>> delegatesManager;
private List<Base> items;
public BaseAdapter(Activity activity, List<Base> items) {
this.items = items;
delegatesManager = new AdapterDelegatesManager<>();
delegatesManager.addDelegate(new AAdapterDelegate(activity))
.addDelegate(new BAdapterDelegate(activity));
}
@Override
public int getItemViewType(int position) {
return delegatesManager.getItemViewType(items, position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return delegatesManager.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
delegatesManager.onBindViewHolder(items, position, holder);
}
@Override
public int getItemCount() {
return items.size();
}
}
需要對每個類進行進行具體設定, 這裡以A為例.
public class AAdapterDelegate extends AdapterDelegate<List<Base>> {
private LayoutInflater inflater;
public AAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
@Override
public boolean isForViewType(@NonNull List<Base> items, int position) {
return items.get(position) instanceof A;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.rv_cv_img_txt_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull List<Base> items, int position,
@NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
CatViewHolder vh = (CatViewHolder) holder;
A cat = (A) items.get(position);
vh.name.setText(cat.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public ImageView img;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.tv_txt);
img = (ImageView) itemView.findViewById(R.id.iv_img);
}
}
}
最後
看完這篇應該是對RecyclerView有個大體認識了, 多練習練習就會得心應手起來了. 那還是有一點, 就像分隔線庫的幾次不理想表現, 具體專案要求還是要具體對待, 開源庫也不是萬能的. 最近不是又有什麼開源專案套殼事件了嘛, 別人一開源就說自己有自主產權了真的好嗎? 喜歡記得點贊或者關注我哦, 有意見或者建議評論區見~
相關文章
- Flutter從入門到寄幾玩兒Flutter
- makefile從入門到入門
- scala 從入門到入門+
- Minecraft 從入門到入坑(邊玩遊戲邊學程式設計)Raft遊戲程式設計
- kafka從入門到關門Kafka
- MyBatis從入門到精通(一):MyBatis入門MyBatis
- Vite 從入門到精通,玩轉新時代前端構建法則Vite前端
- 慕課 玩轉資料結構 從入門到進階 (草稿)資料結構
- SA:從入門到入土
- Python從入門到精通Python
- vim從入門到精通
- Jdbc從入門到入土JDBC
- Thymeleaf從入門到精通
- Eclipse從入門到精通Eclipse
- Shell從入門到精通
- Promise從入門到精通Promise
- LESS從入門到精通
- Git 從入門到精通Git
- babel從入門到跑路Babel
- SAP從入門到精通
- Service Worker 從入門到出門
- 0到1,Celery從入門到出家
- deepspeed從入門到放棄
- NumPy從入門到放棄
- Kaizen如何從入門到精通?AI
- Linux從入門到精通(二)Linux
- Celery框架從入門到精通框架
- Docker 從入門到掉坑Docker
- XXE從入門到放棄
- ElasticSearch 7.8.1 從入門到精通Elasticsearch
- Vue 從入門到放棄Vue
- Nginx從入門到放棄Nginx
- Kotlin從入門到跑路(一)Kotlin
- 幀數,從入門到入土
- Redis從入門到進階Redis
- RabbitMQ 從入門到精通 (一)MQ
- RxJava 從入門到全解析RxJava
- Locust 從入門到實戰