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的動畫以及分割線的格式。這兩個屬性,我們下回再說。