Android中,最复杂的原生控件就是ListView,在support-v7中,Google提供了一种功能更加丰富的控件来代替ListView,那就是RecyclerView,本篇文章,就来解析一下ListView与RecyclerView。
使用ListView控件需要为设置一个Adapter:
public class listadapter extends BaseAdapter {
private Context context;
private List<listitem> list;
class ViewHolder {
ImageView imageView;
TextView textView;
}
public listadapter(Context context, List<listitem> list) {
this.context = context;
this.list = list;
}
public listitem getItem(int position) {
return list.get(position);
}
public long getItemId(int postion) {
return postion;
}
public int getCount() {
return list.size();
}
public View getView(int position, View convertview, ViewGroup parent) {
listitem listitem = getItem(position);
View view;
ViewHolder viewHolder;
if(convertview == null) {
int id = getItemViewType(position);
viewHolder = new ViewHolder();
if(id == 0) {
view = LayoutInflater.from(context).inflate(R.layout.listitem_layout0, parent, false);
viewHolder.imageView = (ImageView) view.findViewById(R.id.image0);
viewHolder.textView = (TextView) view.findViewById(R.id.name0);
view.setTag(viewHolder);
} else {
view = LayoutInflater.from(context).inflate(R.layout.listitem_layout1, parent, false);
viewHolder.imageView = (ImageView) view.findViewById(R.id.image1);
viewHolder.textView = (TextView) view.findViewById(R.id.name1);
view.setTag(viewHolder);
}
} else {
view = convertview;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.imageView.setImageResource(listitem.getImageId());
viewHolder.textView.setText(listitem.getName());
return view;
}
@Override
public int getItemViewType(int position) {
if(position < 6) return 0;
else return 1;
}
@Override
public int getViewTypeCount() {
return 2;
}
}
上面的代码中,Adapter继承自BaseAdapter, 复写了六种方法。其中
public listitem getItem(int position) { return list.get(position); } public long getItemId(int postion) { return postion; } public int getCount() { return list.size(); } public View getView(int position, View convertview, ViewGroup parent) {
}
四种方法最为基本(注意getItemId方法的返回值类型为long),getView在每次获取View时都会被调用,里面复写的逻辑尽可能简单。
此外,使用convertview与ViewHolder来对ListView进行优化。
@Override public int getItemViewType(int position) { if(position < 6) return 0; else return 1; } @Override public int getViewTypeCount() { return 2; }复写以上两种方法,可以实现ListView的多种布局。getViewTypeCount方法返回布局类型的数目,getItemViewType方法返回布局类型的序号,范围为0~n-1(n为布局类型的数目)。
ListView还可以直接与Android自带的轻量级数据库sqlite相关联。
相比于ListView,RecyclerView除了需要设置Adapter,还需要设置LayoutManager。
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>
{
private Set<BitmapWorkerTask> taskCollection;
//private List<square> squareList = new ArrayList<>();
private LruCache<String, Bitmap> bitmapLruCache;
private RecyclerView recyclerView;
private StaggeredGridLayoutManager layoutManager;
private int firstVisibleItem, lastVisibleItem;
private boolean isFirstEnter = true;
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
parent.getContext()).inflate(R.layout.squareitem, parent,
false));
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position)
{
final String url = image.imageThumbUrls[position];
ImageView bg = holder.bg;
bg.setTag(url);
Bitmap bitmap = getBitmapFromMemoryCache(url);
if (bitmap != null) {
bg.setImageBitmap(bitmap);
} else {
bg.setImageResource(R.color.black_overlay);
}
}
@Override
public int getItemCount()
{
return image.imageThumbUrls.length;
}
class MyViewHolder extends RecyclerView.ViewHolder
{
ImageView bg;
public MyViewHolder(View view)
{
super(view);
bg = (ImageView) view.findViewById(R.id.bgimage);
}
}
public RecyclerViewAdapter(RecyclerView recyclerView) {
//this.squareList = squareList;
this.recyclerView = recyclerView;
layoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager();
taskCollection = new HashSet<>();
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
bitmapLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
System.out.println("CacheSize : " + cacheSize);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == SCROLL_STATE_IDLE) {
loadBitmaps(firstVisibleItem, lastVisibleItem);
} else {
cancelAllTasks();
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItem = layoutManager.findFirstVisibleItemPositions(null)[0];
lastVisibleItem = layoutManager.findLastVisibleItemPositions(null)[1];
System.out.println("firstvisibleitem : " + firstVisibleItem);
System.out.println("lastvisibleitem : " + lastVisibleItem);
if (isFirstEnter && lastVisibleItem - firstVisibleItem > 0) {
loadBitmaps(firstVisibleItem, lastVisibleItem);
isFirstEnter = false;
}
}
});
}
private void loadBitmaps(int firstVisibleItem, int lastVisibleItem) {
try {
for (int i = firstVisibleItem; i <= lastVisibleItem; i ++) {
String imageUrl = image.imageThumbUrls[i];
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(imageUrl);
System.out.println("task is executing");
} else {
ImageView imageView = (ImageView) recyclerView.findViewWithTag(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("task failed");
}
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
bitmapLruCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemoryCache(String key) {
return bitmapLruCache.get(key);
}
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
Bitmap bitmap = downloadBitmap(params[0]);
if (bitmap != null) {
addBitmapToMemoryCache(params[0], bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
ImageView imageView = (ImageView) recyclerView.findViewWithTag(imageUrl);
if(imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
taskCollection.remove(this);
}
private Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap = null;
HttpURLConnection con = null;
try {
URL url = new URL(imageUrl);
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5000);
con.setReadTimeout(10000);
bitmap = BitmapFactory.decodeStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();;
} finally {
if (con != null) {
con.disconnect();
}
}
return bitmap;
}
}
}
上面的代码中使用了LRUCache对图片进行缓存,同时对RecyclerView进行了滑动监听,仅在静止时加载图片。
RecyclerView使用的Adapter继承了RecyclerView.Adapter<T>,泛型T为RecyclerView.ViewHolder类型,需要在Adapter中自己设置,继承RecyclerView.ViewHolder,重写构造函数。其他需要重写的三个函数:
@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) @Override public void onBindViewHolder(MyViewHolder holder, int position) @Override public int getItemCount() onCreateViewHolder方法返回ViewHolder。 在onBindViewHolder方法中设置Item的View,相当于ListView中的getView方法,不过onBinderViewHolder无需返回View。 getItemCount方法返回Item的数量,相当于ListView中的getCount方法。 LayoutManager表示RecyclerView的布局格式。不同于ListView的线性的颇显单调的布局,RecyclerView提供了LinearLayourManager(线性布局),GridLayoutManager(方格布局) 和StaggeredGridLayoutManager(瀑布流布局)三种布局格式。一次性解决了ListView和GridView,还提供了瀑布流的布局格式。 此外,还可以同过setItemAnimator
和addItemDecoration
方法来设置RecyclerView的增删Item的动画以及分割线的格式。这两个属性,我们下回再说。