天天看點

RecyclerView可複用擴充卡的封裝(1)0、前言1、RecyclerView的基本用法2、ViewHolder的優化分析3、Adapter的優化分析

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類似,并且效率更高,如下圖所示:

RecyclerView可複用擴充卡的封裝(1)0、前言1、RecyclerView的基本用法2、ViewHolder的優化分析3、Adapter的優化分析

封裝後的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);
           

運作效果如下圖所示:

RecyclerView可複用擴充卡的封裝(1)0、前言1、RecyclerView的基本用法2、ViewHolder的優化分析3、Adapter的優化分析