0、前言
如今RecyclerView已經逐漸取代ListView,成為Android開發中最常用的控件之一,它在用法上也與ListView類似,為了友善使用,也可以參照ListView封裝出一個可複用擴充卡。本文主要根據鴻洋大神的文章編寫,并結合了自己對于RecyclerView基本用法的一些總結。
1、RecyclerView的基本用法
首先回顧下RecyclerView的基本用法,建立一個RecyclerViewDemo項目,打開app/build.gradle檔案,在dependencies閉包中添加相應的依賴庫,如下所示:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
}
修改activity_main.xml中的代碼,如下所示:
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
然後在layout目錄下為清單項建立一個自定義的布局檔案recyclerview_item.xml,在這個布局中,定義了一個TextView用于顯示文本,一個ImageView用于顯示圖檔,代碼如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="horizontal">
<TextView
android:id="@+id/text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/app_name"
android:textSize="20sp" />
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
提取清單項的屬性:一個字元串和一張圖檔的ID,建立實體類ItemBean,作為RecyclerView擴充卡的适配類型,代碼如下所示:
public class ItemBean {
private String text;
private int imageId;
public ItemBean(String text, int inmageId) {
this.text = text;
this.imageId = inmageId;
}
public String getText() {
return text;
}
public int getImageId() {
return imageId;
}
}
接下來需要建立一個自定義的擴充卡,建立類RecyclerViewAdapter,繼承RecyclerView.Adapter,并将泛型指定為RecyclerViewAdapter.ViewHolder,ViewHolder是在RecyclerViewAdapter中定義的一個内部類,代碼如下所示:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private List<ItemBean> datas;
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
ImageView imageView;
public ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.text_view);
imageView = itemView.findViewById(R.id.image_view);
}
}
public RecyclerViewAdapter(List<ItemBean> datas) {
this.datas = datas;
}
@Override
public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) {
ItemBean bean = datas.get(position);
holder.textView.setText(bean.getText());
holder.imageView.setImageResource(bean.getImageId());
}
@Override
public int getItemCount() {
return datas.size();
}
}
其中,onCreateViewHolder()方法是用于加載布局檔案,建立ViewHolder執行個體的。onBindViewHolder()方法是用于對RecyclerView子項的資料進行指派的,會在每個子項被滾動到螢幕内的時候執行。
2、ViewHolder的優化分析
通過上文對RecyclerView基本用法的回顧,ViewHolder內建了清單項布局内各種控件的執行個體,如果要封裝一個通用的ViewHolder,可以采用容器來存儲控件,然後定義一個方法通過ID擷取控件。當key的值為int類型,value的值為Object類型時,推薦使用SparseArray<T>,從源碼的注釋可以看出它和Map類似,并且效率更高,如下圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuYDO2AjMzYTM0IjMxgTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
封裝後的ViewHolder代碼如下所示:
public class ViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;
public ViewHolder(Context context, View itemView, ViewGroup parent) {
super(itemView);
this.mViews = new SparseArray<View>();
this.mConvertView = itemView;
this.mContext = context;
}
public static ViewHolder getViewHolder(Context context, ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent, false);
ViewHolder holder = new ViewHolder(context, itemView, parent);
return holder;
}
// 通過ID擷取控件
public <T extends View>T getViewById(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
}
對ViewHolder還能進一步優化,因為ViewHolder的主要功能是通過ID擷取控件,使用頻率較高,是以可以考慮專門封裝擷取某一控件并進行指派的方法,完整的ViewHolder的代碼如下所示:
public class ViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;
public ViewHolder(Context context, View itemView, ViewGroup parent) {
super(itemView);
this.mViews = new SparseArray<View>();
this.mConvertView = itemView;
this.mContext = context;
}
public static ViewHolder getViewHolder(Context context, ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent, false);
ViewHolder holder = new ViewHolder(context, itemView, parent);
return holder;
}
// 通過ID擷取控件
public <T extends View>T getViewById(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
// 通過字元串設定TextView的值
public ViewHolder setText(int viewId, String text) {
TextView tv = getViewById(viewId);
tv.setText(text);
return this;
}
// 通過資源檔案設定ImageView的值
public ViewHolder setImageResource(int viewId, int resId) {
ImageView iv = getViewById(viewId);
iv.setImageResource(resId);
return this;
}
// 通過位圖設定ImageView的值
public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
ImageView iv = getViewById(viewId);
iv.setImageBitmap(bitmap);
return this;
}
public ViewHolder setImageURI(int viewId, String url) {
ImageView iv = getViewById(viewId);
// 通過網絡圖檔設定ImageView的值
return this;
}
public ViewHolder setOnClickListener(
int viewId, View.OnClickListener listener) {
View view = getViewById(viewId);
view.setOnClickListener(listener);
return this;
}
}
3、Adapter的優化分析
接下來繼續對Adapter進行優化分析,通過觀察自定義的Adapter類可以發現,不同的Adapter類實作的方法基本相同,是以可以将這些方法抽取成一個抽象類CommonAdapter<T>,不同的Bean可以使用泛型來支援,代碼如下所示:
public abstract class CommonAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
private Context mContext;
private List<T> mDatas;
private int mLayoutId;
private LayoutInflater mInflater;
public CommonAdapter(Context context, int layoutId, List<T> datas) {
this.mContext = context;
this.mDatas = datas;
this.mLayoutId = layoutId;
this.mInflater = LayoutInflater.from(context);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = ViewHolder.getViewHolder(mContext, parent, mLayoutId);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
convert(holder, mDatas.get(position));
}
@Override
public int getItemCount() {
return mDatas.size();
}
public abstract void convert(ViewHolder holder, T t);
}
在onBindViewHolder()方法中調用了一個公開的抽象方法convertView()來擷取并操作控件,繼承或通過匿名内部類來實作此方法。不過内部類這種方式并不被推薦,因為RecyclerView中可能有許多的邏輯處理,會使Activity的代碼看起來很臃腫,是以建議建立CommonAdapter的子類,在子類中對清單項内的控件進行操作,代碼如下所示:
public class RecyclerViewAdapter extends CommonAdapter<ItemBean> {
public RecyclerViewAdapter(Context context, int layoutId, List<ItemBean> datas) {
super(context, layoutId, datas);
}
@Override
public void convert(ViewHolder holder, ItemBean itemBean) {
holder.setText(R.id.text_view, itemBean.getText())
.setImageResource(R.id.image_view, itemBean.getImageId());
}
}
至此,RecyclerView可複用擴充卡的封裝及性能優化就先告一段落了,在Activity中的使用方式如下所示:
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, R.layout.recyclerview_item, datas);
recyclerView.setAdapter(adapter);
運作效果如下圖所示: