这个题目放在草稿箱里面许久了,一直没有动力提笔。趁现在公司人还没有来齐,工作量还不是很大,就挤出来时间来把它完善了。
我们知道,RecyclerView是经典的ListView的进化与升华,它比ListView更加灵活,但也因此引入了一定的复杂性。最新的v7支持包新添加了RecyclerView。
我们知道,ListView通过使用ViewHolder来提升性能。ViewHolder通过保存item中使用到的控件的引用来减少findViewById的调用,以此使ListView滑动得更加顺畅。但这种模式即使不使用也无妨。
对于还不是很熟悉RecyclerView的程序员,可以先从这两篇文章里面了解一下它的基本用法:
这里对RecyclerView的基本使用方式,就不再赘述,只讨论RecyclerView中的ViewHolder的缓存机制。其实它的缓存机制大致原理跟ListView是相似的,只是在具体的实现细节上面有些差异。对于这些RecyclerView的缓存机制,Android L新控件RecyclerView简介这篇文章的结尾进行了总结性的概括,这里讨论的内容要远比这篇简介的内容要复杂和多。
首先,我定义了三个不同的Adapter,里面分别对应了三种情况:item类型只有一种;item类型有两种;item类型有三种。里面均重写了onCreateViewHolder方法,用于记录生成的ViewHolder或者View的数目。内容可以从Log里面查询得到。三个Adapter的细节分别是:
1 /** 2 * @Title: MyRVAdapter.java 3 * @Package com.example.recyclerviewtest.adapter 4 * @Description: TODO 5 * @author SilentKnight || happychinapc[at]gmail[dot]com 6 * @date 2015 2015年1月22日 下午4:54:25 7 * @version V1.0.0 8 */ 9 package com.example.recyclerviewtest.adapter; 10 11 import java.util.List; 12 13 import android.content.Context; 14 import android.support.v7.widget.RecyclerView; 15 import android.util.Log; 16 import android.view.LayoutInflater; 17 import android.view.View; 18 import android.view.ViewGroup; 19 import android.view.View.OnClickListener; 20 import android.widget.TextView; 21 import android.widget.Toast; 22 23 import com.example.recyclerviewtest.R; 24 25 /** 26 * @ClassName: MyRVAdapter 27 * @Description: TODO 28 * @author SilentKnight || happychinapc@gmail.com 29 * @date 2015年1月22日 下午4:54:25 30 * 31 */ 32 public class MyRVAdapter extends RecyclerView.Adapter<MyRVAdapter.ViewHolder> { 33 private static int COUNT_CACHE_VIEW = 0; 34 private static final String ADAPTER_TAG = MyRVAdapter.class.getSimpleName(); 35 private List<String> dataSet; 36 private Context context; 37 38 public MyRVAdapter(Context context, List<String> dataSet) { 39 this.context = context; 40 this.dataSet = dataSet; 41 } 42 43 /* 44 * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p> 45 * 46 * @params @param position 47 * 48 * @params @return 49 * 50 * @overrided @see 51 * android.support.v7.widget.RecyclerView.Adapter#getItemId(int) 52 */ 53 @Override 54 public long getItemId(int position) { 55 // TODO Auto-generated method stub 56 return position; 57 } 58 59 /* 60 * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p> 61 * 62 * @params @return 63 * 64 * @overrided @see 65 * android.support.v7.widget.RecyclerView.Adapter#getItemCount() 66 */ 67 @Override 68 public int getItemCount() { 69 // TODO Auto-generated method stub 70 return dataSet.size(); 71 } 72 73 /* 74 * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p> 75 * 76 * @params @param arg0 77 * 78 * @params @param arg1 79 * 80 * @overrided @see 81 * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder 82 * (android.support.v7.widget.RecyclerView.ViewHolder, int) 83 */ 84 @Override 85 public void onBindViewHolder(ViewHolder holder, final int arg1) { 86 // TODO Auto-generated method stub 87 holder.tv.setOnClickListener(new OnClickListener() { 88 89 @Override 90 public void onClick(View v) { 91 // TODO Auto-generated method stub 92 Toast.makeText(context, "You clickd TextView at index of "+arg1, 93 Toast.LENGTH_SHORT).show(); 94 } 95 }); 96 holder.tv.setText(dataSet.get(arg1)); 97 } 98 99 /* 100 * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 101 * 102 * @params @param arg0 103 * 104 * @params @param arg1 105 * 106 * @params @return 107 * 108 * @overrided @see 109 * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 110 * (android.view.ViewGroup, int) 111 */ 112 113 @Override 114 public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 115 // TODO Auto-generated method stub 116 Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW); 117 View itemLayout = LayoutInflater.from(viewGroup.getContext()).inflate( 118 R.layout.recycler_view_item_layout_tv, null); 119 return new ViewHolder(itemLayout); 120 } 121 122 public static class ViewHolder extends RecyclerView.ViewHolder { 123 public TextView tv; 124 125 /** 126 * <p> 127 * Title: MainActivity.java 128 * </p> 129 * <p> 130 * Description: 131 * </p> 132 * 133 * @param @param itemView 134 */ 135 public ViewHolder(View itemView) { 136 super(itemView); 137 // TODO Auto-generated constructor stub 138 tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 139 } 140 } 141 }
1 /** 2 * @Title: MyRVAdapter2.java 3 * @Package com.example.recyclerviewtest.adapter 4 * @Description: TODO 5 * @author SilentKnight || happychinapc[at]gmail[dot]com 6 * @date 2015 2015年1月22日 下午4:55:31 7 * @version V1.0.0 8 */ 9 package com.example.recyclerviewtest.adapter; 10 11 import java.util.List; 12 13 import android.content.Context; 14 import android.content.Intent; 15 import android.support.v7.widget.RecyclerView; 16 import android.util.Log; 17 import android.view.LayoutInflater; 18 import android.view.View; 19 import android.view.ViewGroup; 20 import android.view.View.OnClickListener; 21 import android.widget.ImageView; 22 import android.widget.TextView; 23 import android.widget.Toast; 24 25 import com.example.recyclerviewtest.GridViewHorizontalTest; 26 import com.example.recyclerviewtest.GridViewVerticalTest; 27 import com.example.recyclerviewtest.R; 28 29 /** 30 * @ClassName: MyRVAdapter2 31 * @Description: TODO 32 * @author SilentKnight || happychinapc@gmail.com 33 * @date 2015年1月22日 下午4:55:31 34 * 35 */ 36 public class MyRVAdapter2 extends RecyclerView.Adapter<MyRVAdapter2.ViewHolder> { 37 private static int COUNT_CACHE_VIEW_1 = 0; 38 private static int COUNT_CACHE_VIEW_2 = 0; 39 private static final String ADAPTER_TAG = MyRVAdapter2.class 40 .getSimpleName(); 41 private static final int TYPE_TV = 0x000; 42 private static final int TYPE_IV = 0x0001; 43 private List<String> dataSet; 44 private Context context; 45 46 public MyRVAdapter2(Context context, List<String> dataSet) { 47 this.context = context; 48 this.dataSet = dataSet; 49 } 50 51 /* 52 * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p> 53 * 54 * @params @param position 55 * 56 * @params @return 57 * 58 * @overrided @see 59 * android.support.v7.widget.RecyclerView.Adapter#getItemId(int) 60 */ 61 @Override 62 public long getItemId(int position) { 63 // TODO Auto-generated method stub 64 return position; 65 } 66 67 /* 68 * (non-avadoc) <p>Title: getItemViewType</p> <p>Description: </p> 69 * 70 * @params @param position 71 * 72 * @params @return 73 * 74 * @overrided @see 75 * android.support.v7.widget.RecyclerView.Adapter#getItemViewType(int) 76 */ 77 @Override 78 public int getItemViewType(int position) { 79 // TODO Auto-generated method stub 80 if (position % 2 == 0) { 81 return TYPE_TV; 82 } else { 83 return TYPE_IV; 84 } 85 } 86 87 /* 88 * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p> 89 * 90 * @params @return 91 * 92 * @overrided @see 93 * android.support.v7.widget.RecyclerView.Adapter#getItemCount() 94 */ 95 @Override 96 public int getItemCount() { 97 // TODO Auto-generated method stub 98 return dataSet.size(); 99 } 100 101 /* 102 * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p> 103 * 104 * @params @param arg0 105 * 106 * @params @param arg1 107 * 108 * @overrided @see 109 * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder 110 * (android.support.v7.widget.RecyclerView.ViewHolder, int) 111 */ 112 @Override 113 public void onBindViewHolder(ViewHolder holder, int arg1) { 114 // TODO Auto-generated method stub 115 if (getItemViewType(arg1) == TYPE_TV) { 116 holder.tv.setOnClickListener(new OnClickListener() { 117 118 @Override 119 public void onClick(View v) { 120 // TODO Auto-generated method stub 121 context.startActivity(new Intent(context, 122 GridViewHorizontalTest.class)); 123 } 124 }); 125 holder.tv.setText(dataSet.get(arg1)); 126 } else { 127 holder.iv.setOnClickListener(new OnClickListener() { 128 129 @Override 130 public void onClick(View v) { 131 // TODO Auto-generated method stub 132 context.startActivity(new Intent(context, 133 GridViewVerticalTest.class)); 134 } 135 }); 136 } 137 } 138 139 /* 140 * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 141 * 142 * @params @param arg0 143 * 144 * @params @param arg1 145 * 146 * @params @return 147 * 148 * @overrided @see 149 * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 150 * (android.view.ViewGroup, int) 151 */ 152 153 @Override 154 public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 155 // TODO Auto-generated method stub 156 if (getItemViewType(arg1) == TYPE_TV) { 157 Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW_1); 158 View itemLayout = LayoutInflater.from(viewGroup.getContext()) 159 .inflate(R.layout.recycler_view_item_layout_tv, null); 160 return new ViewHolder(itemLayout, TYPE_TV); 161 } else { 162 Log.i(ADAPTER_TAG, "itemIV---" + ++COUNT_CACHE_VIEW_2); 163 View itemLayout = LayoutInflater.from(viewGroup.getContext()) 164 .inflate(R.layout.recycler_view_item_layout_iv, null); 165 return new ViewHolder(itemLayout, TYPE_IV); 166 } 167 } 168 169 public static class ViewHolder extends RecyclerView.ViewHolder { 170 public TextView tv; 171 public ImageView iv; 172 173 /** 174 * <p> 175 * Title: MainActivity.java 176 * </p> 177 * <p> 178 * Description: 179 * </p> 180 * 181 * @param @param itemView 182 */ 183 public ViewHolder(View itemView, int itemType) { 184 super(itemView); 185 // TODO Auto-generated constructor stub 186 if (itemType == TYPE_TV) { 187 tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 188 } else { 189 iv = (ImageView) itemView.findViewById(R.id.rv_item_iv); 190 } 191 } 192 } 193 }
1 /** 2 * @Title: MyRVAdapter3.java 3 * @Package com.example.recyclerviewtest.adapter 4 * @Description: TODO 5 * @author SilentKnight || happychinapc[at]gmail[dot]com 6 * @date 2015 2015年1月22日 下午4:56:33 7 * @version V1.0.0 8 */ 9 package com.example.recyclerviewtest.adapter; 10 11 import java.util.List; 12 13 import android.content.Context; 14 import android.support.v7.widget.RecyclerView; 15 import android.util.Log; 16 import android.view.LayoutInflater; 17 import android.view.View; 18 import android.view.ViewGroup; 19 import android.view.View.OnClickListener; 20 import android.widget.ImageView; 21 import android.widget.TextView; 22 import android.widget.Toast; 23 24 import com.example.recyclerviewtest.R; 25 26 /** 27 * @ClassName: MyRVAdapter3 28 * @Description: TODO 29 * @author SilentKnight || happychinapc@gmail.com 30 * @date 2015年1月22日 下午4:56:33 31 * 32 */ 33 public class MyRVAdapter3 extends RecyclerView.Adapter<MyRVAdapter3.ViewHolder> { 34 private static int COUNT_CACHE_VIEW_1 = 0; 35 private static int COUNT_CACHE_VIEW_2 = 0; 36 private static int COUNT_CACHE_VIEW_3 = 0; 37 private static final String ADAPTER_TAG = MyRVAdapter3.class 38 .getSimpleName(); 39 private static final int TYPE_TV = 0x000; 40 private static final int TYPE_IV = 0x0001; 41 private static final int TYPE_TV_2 = 0x0002; 42 private List<String> dataSet; 43 private Context context; 44 45 public MyRVAdapter3(Context context, List<String> dataSet) { 46 this.context = context; 47 this.dataSet = dataSet; 48 } 49 50 /* 51 * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p> 52 * 53 * @params @param position 54 * 55 * @params @return 56 * 57 * @overrided @see 58 * android.support.v7.widget.RecyclerView.Adapter#getItemId(int) 59 */ 60 @Override 61 public long getItemId(int position) { 62 // TODO Auto-generated method stub 63 return position; 64 } 65 66 /* 67 * (non-avadoc) <p>Title: getItemViewType</p> <p>Description: </p> 68 * 69 * @params @param position 70 * 71 * @params @return 72 * 73 * @overrided @see 74 * android.support.v7.widget.RecyclerView.Adapter#getItemViewType(int) 75 */ 76 @Override 77 public int getItemViewType(int position) { 78 // TODO Auto-generated method stub 79 if (position % 3 == 0) { 80 return TYPE_TV; 81 } else if (position % 3 == 1) { 82 return TYPE_IV; 83 } else { 84 return TYPE_TV_2; 85 } 86 } 87 88 /* 89 * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p> 90 * 91 * @params @return 92 * 93 * @overrided @see 94 * android.support.v7.widget.RecyclerView.Adapter#getItemCount() 95 */ 96 @Override 97 public int getItemCount() { 98 // TODO Auto-generated method stub 99 return dataSet.size(); 100 } 101 102 /* 103 * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p> 104 * 105 * @params @param arg0 106 * 107 * @params @param arg1 108 * 109 * @overrided @see 110 * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder 111 * (android.support.v7.widget.RecyclerView.ViewHolder, int) 112 */ 113 @Override 114 public void onBindViewHolder(ViewHolder holder, int arg1) { 115 // TODO Auto-generated method stub 116 if (getItemViewType(arg1) == TYPE_TV) { 117 holder.tv.setOnClickListener(new OnClickListener() { 118 119 @Override 120 public void onClick(View v) { 121 // TODO Auto-generated method stub 122 Toast.makeText(context, "You clickd TextView", 123 Toast.LENGTH_SHORT).show(); 124 } 125 }); 126 holder.tv.setText(dataSet.get(arg1)); 127 } else if (getItemViewType(arg1) == TYPE_IV) { 128 holder.iv.setOnClickListener(new OnClickListener() { 129 130 @Override 131 public void onClick(View v) { 132 // TODO Auto-generated method stub 133 134 Toast.makeText(context, "You clickd ImageView", 135 Toast.LENGTH_SHORT).show(); 136 } 137 }); 138 } else { 139 140 } 141 } 142 143 /* 144 * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 145 * 146 * @params @param arg0 147 * 148 * @params @param arg1 149 * 150 * @params @return 151 * 152 * @overrided @see 153 * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 154 * (android.view.ViewGroup, int) 155 */ 156 157 @Override 158 public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 159 // TODO Auto-generated method stub 160 if (getItemViewType(arg1) == TYPE_TV) { 161 Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW_1); 162 View itemLayout = LayoutInflater.from(viewGroup.getContext()) 163 .inflate(R.layout.recycler_view_item_layout_tv, null); 164 return new ViewHolder(itemLayout, TYPE_TV); 165 } else if (getItemViewType(arg1) == TYPE_IV) { 166 Log.i(ADAPTER_TAG, "itemIV---" + ++COUNT_CACHE_VIEW_2); 167 View itemLayout = LayoutInflater.from(viewGroup.getContext()) 168 .inflate(R.layout.recycler_view_item_layout_iv, null); 169 return new ViewHolder(itemLayout, TYPE_IV); 170 } else { 171 Log.i(ADAPTER_TAG, "itemTV2---" + ++COUNT_CACHE_VIEW_3); 172 View itemLayout = LayoutInflater.from(viewGroup.getContext()) 173 .inflate(R.layout.recycler_view_item_layout_tv_2, null); 174 return new ViewHolder(itemLayout, TYPE_TV_2); 175 } 176 } 177 178 public static class ViewHolder extends RecyclerView.ViewHolder { 179 public TextView tv; 180 public TextView tv2; 181 public ImageView iv; 182 183 /** 184 * <p> 185 * Title: MainActivity.java 186 * </p> 187 * <p> 188 * Description: 189 * </p> 190 * 191 * @param @param itemView 192 */ 193 public ViewHolder(View itemView, int itemType) { 194 super(itemView); 195 // TODO Auto-generated constructor stub 196 if (itemType == TYPE_TV) { 197 tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 198 } else if (itemType == TYPE_IV) { 199 iv = (ImageView) itemView.findViewById(R.id.rv_item_iv); 200 } else { 201 tv2 = (TextView) itemView.findViewById(R.id.rv_item_tv); 202 } 203 } 204 } 205 }
同时,这个测试工程里面只有一个Activity,它的详情如下:
1 package com.example.recyclerviewtest; 2 3 import java.util.List; 4 5 import com.example.recyclerviewtest.adapter.MyRVAdapter; 6 import com.example.recyclerviewtest.adapter.MyRVAdapter2; 7 import com.example.recyclerviewtest.adapter.MyRVAdapter3; 8 import com.example.recyclerviewtest.util.Utils; 9 10 import android.support.v7.app.ActionBarActivity; 11 import android.support.v7.widget.LinearLayoutManager; 12 import android.support.v7.widget.RecyclerView; 13 import android.os.Bundle; 14 import android.widget.LinearLayout; 15 16 public class MainActivity extends ActionBarActivity { 17 private static final String TAG = MainActivity.class.getSimpleName(); 18 19 private RecyclerView rvHorizontal; 20 private RecyclerView rvVertical; 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 rvHorizontal = (RecyclerView) findViewById(R.id.recyclier_view_horizontal); 27 rvVertical = (RecyclerView) findViewById(R.id.recyclier_view_vertical); 28 List<String> dataSet = Utils.generateDataSet(); 29 LinearLayoutManager layoutManager = new LinearLayoutManager(this, 30 LinearLayout.HORIZONTAL, false); 31 rvHorizontal.setLayoutManager(layoutManager); 32 layoutManager = new LinearLayoutManager(this, LinearLayout.VERTICAL, 33 false); 34 rvVertical.setLayoutManager(layoutManager); 35 MyRVAdapter adapter = new MyRVAdapter(this, dataSet); 36 MyRVAdapter2 adapter2 = new MyRVAdapter2(this, dataSet); 37 MyRVAdapter3 adapter3 = new MyRVAdapter3(this, dataSet); 38 rvHorizontal.setAdapter(adapter2); 39 rvVertical.setAdapter(adapter); 40 } 41 42 }
对于你想测试的三种情况,你可以通达代码rvHorizontal.setAdapter(adapter2)和rvVertical.setAdapter(adapter)来进行测试。在测试的过程中,可以通过改变RecyclerView的宽度来测试显示ViewHolder的数目。比如,item的宽度为60dp,你可以分别测试下RecyclerView的宽度分别为60dp、120dp和180dp情况下log的打印情况。其中,activity的布局文件如下:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context="com.example.recyclerviewtest.MainActivity" > 10 11 <android.support.v7.widget.RecyclerView 12 android:id="@+id/recyclier_view_horizontal" 13 android:layout_width="240dp" 14 android:layout_height="70dp" 15 android:layout_alignParentTop="true" 16 android:layout_centerHorizontal="true" 17 android:scrollbars="horizontal" > 18 </android.support.v7.widget.RecyclerView> 19 20 <android.support.v7.widget.RecyclerView 21 android:id="@+id/recyclier_view_vertical" 22 android:layout_width="match_parent" 23 android:layout_height="360dp" 24 android:layout_below="@id/recyclier_view_horizontal" 25 android:layout_centerHorizontal="true" 26 android:scrollbars="vertical" > 27 </android.support.v7.widget.RecyclerView> 28 29 </RelativeLayout>
其中一个item的布局如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:gravity="center" 6 android:orientation="vertical" > 7 8 <TextView 9 android:id="@+id/rv_item_tv" 10 android:layout_width="60dp" 11 android:layout_height="20dp" 12 android:layout_gravity="center" 13 android:background="@drawable/item_selector" 14 android:clickable="true" 15 android:focusable="true" 16 android:focusableInTouchMode="true" 17 android:gravity="center" > 18 </TextView> 19 20 </LinearLayout>
总之,测试的结论是这样的:在只有一种item的情况下,缓存的ViewHolder的数目为RecyclerView在滑动过程中所能在一屏内容纳的最大item个数+2。比如,在一个屏幕中只有item A可以显示,在滑动的过程最多可以出现6个item(这个最多是指所有item的个数,包括显示完全和显示不完全的总数),那么ViewHolder的缓存个数将会是8;而有至少两种item显示的情况下,每种item的ViewHolder的缓存个数为单种item在一屏内最大显示个数+1;比如,有3种item A,B,C,在滑动的过程,item A最多的情况下一屏显示了2个,那个item A对应的ViewHolder的缓存个数就是3个。
工程源文件可以从下面的链接打开下载。