Android RecyclerView中的ViewHolder

在使用android提供的組件以列表的格式顯示數據時,使用過ListView組件和RecyclerView組件。目前通常推薦使用RecyclerView,由於RecyclerView自己的緩存和效率比ListView高,且支持靈活的佈局方式,因此會被你們採用。相信你們在使用ListView時,若是要顯示的數據多,確定多會想到優化Adaper的getView()方法,下面給出一個例子:android

public class UsersAdapter extends ArrayAdapter<User> {

    private static class ViewHolder {
        TextView name;
        TextView home;
    }

    public UsersAdapter(Context context, ArrayList<User> users) {
       super(context, R.layout.item_user, users);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       User user = getItem(position);    
       ViewHolder viewHolder;
       if (convertView == null) {
          viewHolder = new ViewHolder();
          LayoutInflater inflater = LayoutInflater.from(getContext());
          convertView = inflater.inflate(R.layout.item_user, parent, false);
          viewHolder.name = (TextView) convertView.findViewById(R.id.tvName);
          viewHolder.home = (TextView)convertView.findViewById(R.id.tvHome);
          convertView.setTag(viewHolder);
       } else {
           viewHolder = (ViewHolder) convertView.getTag();
       }
       viewHolder.name.setText(user.name);
       viewHolder.home.setText(user.hometown);
       return convertView;
   }
}

在這邊,咱們在ListView須要用到的Adapter中定義了一個內部類ViewHolder,它存儲了咱們要加載的view的全部子view結構,若是這個view已經被加載過只是暫時被回收, 當須要再次展現的話咱們就不須要從新加載整個view,也不須要經過findViewById()來尋找要加載的view的子view,能夠直接找到這個view,將要展現的數據設置便可返回顯示。緩存

可是在RecyclerView中,咱們並不須要作這麼多,咱們先看一個RecyclerView的簡單使用步驟:app

  • 定義一個RecyclerView的佈局文件以及要展現的item的佈局文件less

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
   
    <android.support.v7.widget.RecyclerView
        android:id="@+id/t_classList"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
</LinearLayout>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp">

<TextView
    android:id="@+id/t_class_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:textSize="19sp" />

<View
    android:layout_width="match_parent"
    android:layout_height="0.5px"
    android:background="@color/whiteGray"
    android:layout_marginTop="6dp"
    android:layout_marginBottom="6dp"
    android:typeface="serif"/>
</LinearLayout>
  • 定義一個adapteride

public class ClassAdapter extends RecyclerView.Adapter<ClassAdapter.ClassViewHolder> {

private List<ClassVO> classVOs;

static class ClassViewHolder extends RecyclerView.ViewHolder {

    View classView;
    TextView className;

    public ClassViewHolder(View itemView) {
        super(itemView);
        classView = itemView;
        className = (TextView) itemView.findViewById(R.id.t_class_name);
    }
}

public ClassAdapter(List<ClassVO> classVOs){
    this.classVOs = classVOs;
}

@Override
public ClassViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragement_class_item, null, false);
    final ClassViewHolder holder = new ClassViewHolder(view);
    holder.classView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            int position = holder.getAdapterPosition();
            ClassVO classVO = classVOs.get(position);
            Toast.makeText(view.getContext(), "you click class "+classVO.getId(), Toast.LENGTH_SHORT).show();
        }
    });
    return holder;
}

@Override
public void onBindViewHolder(ClassViewHolder holder, int position) {
    ClassVO classVO = classVOs.get(position);
    holder.className.setText(classVO.getName());
}

@Override
public int getItemCount() {
    return classVOs.size();
}

}佈局

  • 設置RecyclerView的佈局和adapter優化

recyclerView = (RecyclerView) classListView.findViewById(R.id.t_classList);
layoutManager = new LinearLayoutManager(this.getContext());
recyclerView.setLayoutManager(layoutManager);
if (classVOs!=null){
    adapter = new ClassAdapter(classVOs);
}
recyclerView.setAdapter(adapter);

既然咱們要說的是RecyclerView中的ViewHolder,可是咱們的使用步驟中並無單獨提出ViewHolder,由於在咱們上面說的使用步驟的第二步———「定義一個adapter」,就涉及到ViewHolder。
當咱們自定義一個adapter時,繼承了RecyclerView的一個內部類:ui

/**
     * Base class for an Adapter
     *
     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
     * within a {@link RecyclerView}.</p>
     */
    public static abstract class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;

        /**
         * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
         * an item.
         * <p>
         * This new ViewHolder should be constructed with a new View that can represent the items
         * of the given type. You can either create a new View manually or inflate it from an XML
         * layout file.
         * <p>
         * The new ViewHolder will be used to display items of the adapter using
         * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
         * different items in the data set, it is a good idea to cache references to sub views of
         * the View to avoid unnecessary {@link View#findViewById(int)} calls.
         *
         * @param parent The ViewGroup into which the new View will be added after it is bound to
         *               an adapter position.
         * @param viewType The view type of the new View.
         *
         * @return A new ViewHolder that holds a View of the given view type.
         * @see #getItemViewType(int)
         * @see #onBindViewHolder(ViewHolder, int)
         */
        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

        /**
         * Called by RecyclerView to display the data at the specified position. This method should
         * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
         * position.
         * <p>
         * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the <code>position</code> parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
         * have the updated adapter position.
         *
         * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
         * handle efficient partial bind.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *        item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         */
        public abstract void onBindViewHolder(VH holder, int position);

        /**
         * Called by RecyclerView to display the data at the specified position. This method
         * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
         * the given position.
         * <p>
         * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the <code>position</code> parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
         * have the updated adapter position.
         * <p>
         * Partial bind vs full bind:
         * <p>
         * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
         * {@link #notifyItemRangeChanged(int, int, Object)}.  If the payloads list is not empty,
         * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
         * update using the payload info.  If the payload is empty,  Adapter must run a full bind.
         * Adapter should not assume that the payload passed in notify methods will be received by
         * onBindViewHolder().  For example when the view is not attached to the screen, the
         * payload in notifyItemChange() will be simply dropped.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *               item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         * @param payloads A non-null list of merged payloads. Can be empty list if requires full
         *                 update.
         */

能夠看到,RecyclerView內部已經幫咱們定義好了ViewHolder抽象類,當咱們自定義Adapter時,須要定義好要繼承ViewHolder的類(Adapter<VH extends ViewHolder>)。this

ViewHolder的做用咱們在上文介紹使使用ListView時已經說到,簡而言之就是提升消息,下面咱們看一看源碼中對ViewHolder的介紹:idea

/**
     * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
     *
     * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
     * potentially expensive {@link View#findViewById(int)} results.</p>
     *
     * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
     * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
     * their own custom ViewHolder implementations to store data that makes binding view contents
     * easier. Implementations should assume that individual item views will hold strong references
     * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
     * strong references to extra off-screen item views for caching purposes</p>
     */
    public static abstract class ViewHolder {
        ......
    }

能夠看出兩點重要的地方:

  • Adapter implementations should subclass ViewHolder and add fields for caching potentially expensive {@link View#findViewById(int)} results(adapter應當擁有ViewHolder的子類,而且ViewHolder內部應當存儲一些子view,避免時間代價很大的findViewById操做)

  • Adapters should feel free to use their own custom ViewHolder implementations to store data that makes binding view content easier

其RecyclerView內部定義的ViewHolder類包含不少複雜的屬性,內部使用場景也有不少,而咱們常常使用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView須要一個新類型。item的ViewHolder時調用來建立一個ViewHolder,而onBindViewHolder()方法則當RecyclerView須要在特定位置的item展現數據時調用。

參考文章:

相關文章
相關標籤/搜索