博主最近在学习Volley框架,便想写个Demo练练手,于是这个基于RecyclerView和Volley的瀑布流照片墙应运而生= =。话不多说进入正题~
记得添加Gradle的dependencies,并且导入volley作为Lib。具体可以参考:http://blog.csdn.net/u010940300/article/details/44309405
RecyclerView的基本用法请参考:http://frank-zhu.github.io/android/2015/01/16/android-recyclerview-part-1/以及官方文档。
由于使用volley框架不能在获取图片之前获取具体的图片大小,因此在初始化时博主将一部分图片集体载入,之后在该部分图片全部加载完成之后再调用notifyDataSetChanged()更新RecyclerView。具体代码如下:
private void refreshLraCache(final int refreshNum) {
//加载状态设置为未完成
Log.d("WaterFall","refresh start");
allLoaded=false;
progressBar.setVisibility(View.VISIBLE);
if(refreshNum==0){
allLoaded=true;
progressBar.setVisibility(View.GONE);
}
for(int i=itemCount;i<itemCount+refreshNum;i++){
final int finalI = i;
requestqueue.add(new ImageRequest(ImageURLs.imageUrls[i], new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
//将返回的Bitmap加入内存缓存
cache.put(ImageURLs.imageUrls[finalI],response);
ImageSize imageSize=new ImageSize(response.getWidth(),response.getHeight());
sizeHashMap.put(ImageURLs.imageUrls[finalI],imageSize);
//所有任务完成后将缓存传入Adapter并更新视图
taskCount++;
progress= (int) ((float)taskCount/(refreshNum-1)*100);
progressBar.setProgress(progress);
if(taskCount==refreshNum) {
adapter.setLruCache(cache);
adapter.setSizeHashMap(sizeHashMap);
//更新元素个数
itemCount=itemCount+refreshNum;
adapter.setItemCount(itemCount);
adapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
progress=0;
taskCount=0;
//加载状态设置为全部完成
allLoaded=true;
Log.d("WaterFall","refresh end");
}
Log.d("WaterFall","Task: "+finalI+" completed");
Log.d("WaterFall","remaining memorysize is "+(cacheSize-cache.size()));
}
},spanWidth,0, ImageView.ScaleType.CENTER_CROP,null,null));
}
在触底加载更多时,依然调用该方法加载之后的图片。在图片加载之后,将Bitmap存入之前初始化的LruCache中,并将图片的大小信息封装为ImageSize类存入一个HashMap。当该部分所有图片加载完成时(即taskCount==refreshNum)将LruCache、itemCount、sizeHashMap传入adapter并调用
notifyDataSetChanged()刷新,并将加载状态设置为完成(将用于触底加载的判断,当未完成加载时不会再次调用refreshCache()方法)。
private void setLruCache() {
//获取最大缓存大小,单位M
int maxCacheSize= (int) (Runtime.getRuntime().maxMemory()/1024);
cacheSize=maxCacheSize/8;
cache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
//重写LruCache中计算元素大小方法
return value.getByteCount()/1024;
}
};
}
public class ImageSize {
private int imageWidth;
private int imageHeight;
public ImageSize(int imageWidth,int imageHeight){
this.imageWidth=imageWidth;
this.imageHeight=imageHeight;
}
public int getImageWidth(){
return imageWidth;
}
public int getImageHeight(){
return imageHeight;
}
}
在adapter的onBindViewHolder()中,首先判断内存缓存是否存在。若存在直接显示图片,若不存在则先尝试获取图片尺寸并设置占位图片,然后再次发送volley请求下载图片。
@Override
public void onBindViewHolder(final WaterFallVH holder, int position) {
if(lruCache.get(ImageURLs.imageUrls[position])!=null){
//内存缓存存在直接加载
holder.imageView.setImageBitmap(lruCache.get(ImageURLs.imageUrls[position]));
}else{
//若尺寸信息缓存存在,则获取图片大小信息
if(sizeHashMap.get(ImageURLs.imageUrls[position])!=null) {
int[] size = getLruSize(position);
Bitmap bitmap=resizedImage(BitmapFactory.decodeResource(context.getResources(), R.drawable.loading),size[0],size[1]);
//加载占位图片
holder.imageView.setImageBitmap(bitmap);
}
//内存缓存中不存在该元素,发送Volley请求
sendVolleyRequest(holder,position);
}
最后通过重写OnScrollListener中的onScrollStateChanged()方法判断是否触底以及是否开始加载更多图片。首先判断是否停止滑动,之后判断当前的加载任务是否完成,随后获取每列的最后一个可见View的位置编号,比较之后获得最大的视图编号。若编号与adapter当前的总元素个数相同,则判断已经滑动到最底部,最后判断待加载图片个数是否大于10来决定传入refreshLruCache()中的参数。当剩余图片不足10时,更新图片之后将标志位noMore设置为true,若再触底将不再调用refreshLruCache()并提示没有更多图片。
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//判断是否停止滚动
if(newState==RecyclerView.SCROLL_STATE_IDLE) {
//判断当前加载是否完成
if (allLoaded == true) {
//得到每一列最后一个可见的元素的Position
int[] lastvisibalItem = layoutManager.findLastVisibleItemPositions(null);
int lastposition = 0;
if (columsCount != 1) {
lastposition = Math.max(lastvisibalItem[0], lastvisibalItem[1]);
for (int i = 2; i < columsCount; i++) {
//获取整个视图可见元素中Position的最大值
lastposition = Math.max(lastposition, lastvisibalItem[i]);
}
} else {
lastposition = lastvisibalItem[0];
}
if ((lastposition + 1) == itemCount) {
//当最后一个可见元素的Position与加载的元素总数相等时,判断滑到底部,更新缓存、加载更多
if ((lastposition + 11) <= ImageURLs.imageUrls.length) {
//当还剩余十个以上元素待加载时,加载10个元素
refreshLraCache(refreshSize);
Toast.makeText(context, "Loading...", Toast.LENGTH_SHORT).show();
} else {
if(!noMore) {
//当剩余元素不足十个时,加载剩余元素并提示
int remaining = ImageURLs.imageUrls.length - lastposition - 1;
refreshLraCache(remaining);
Toast.makeText(context, "Loading...", Toast.LENGTH_SHORT).show();
//没有更多图片
noMore = true;
}else {
//没有更多图片时提示
Toast.makeText(context, "No more pictrues", Toast.LENGTH_SHORT).show();
}
}
}
}
}
大概思路就是这样了。什么?你说只有内存缓存太low了。其实volley自带diskCache,不过默认的大小只有5M,还没有内存缓存大= =。不过volley的强大之处就在于其高度的扩展性,于是博主修改了一下volley默认初始化RequestQueue类所传入的diskCache,将其改为了50M。
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir,diskCacheSize), network);
就这么一句就解决了,不得不服啊~volley的设计模式真的十分强大。想要了解更多:http://a.codekk.com/blogs/detail/54cfab086c4761e5001b2542
下面是效果图:
感觉和郭神的一样,我可没有盗图= =。只是我用的他DEMO中的图片地址。
下面附上DEMO地址:https://github.com/xiehaochn/WaterfallDemo
恭迎各路大神指正