最近写了一个瀑布流,将其分享给大家.代码分析及源码在后面贴出.
主要使用到的知识点:
- 图片缓存
- 弱引用
- AsyncTask异步任务
- 懒加载
示例效果图:
主要代码如下
MainActivity.class
package com.sg7.waterfallflow;
import android.app.Activity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends Activity {
private LazyScrollView waterfall_scroll;
private LinearLayout waterfall_container;
private ArrayList<LinearLayout> waterfall_items;
private Display display;
private AssetManager assetManager;
private List<String> image_filenames;
private final String image_path = "images";
private int itemWidth;
private int column_count = 3;// 显示列数
private int page_count = 15;// 每次加载15张图片
private int current_page = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
display = this.getWindowManager().getDefaultDisplay();
// 根据屏幕大小计算每列大小
itemWidth = display.getWidth() / column_count;
assetManager = this.getAssets();
initLayout();
}
private void initLayout() {
waterfall_scroll = (LazyScrollView) findViewById(R.id.waterfall_scroll);
waterfall_scroll.getView();
waterfall_scroll.setOnScrollListener(new LazyScrollView.OnScrollListener() {
@Override
public void onTop() {
// 滚动到最顶端
Log.d("LazyScroll", "Scroll to top");
}
@Override
public void onScroll() {
// 滚动中
Log.d("LazyScroll", "Scroll");
}
@Override
public void onBottom() {
// 滚动到最低端
AddItemToContainer(++current_page, page_count);
}
});
waterfall_container = (LinearLayout) this
.findViewById(R.id.waterfall_container);
waterfall_items = new ArrayList<>();
for (int i = 0; i < column_count; i++) {
LinearLayout itemLayout = new LinearLayout(this);
LinearLayout.LayoutParams itemParam = new LinearLayout.LayoutParams(
itemWidth, LayoutParams.WRAP_CONTENT);
itemLayout.setPadding(2, 2, 2, 2);
itemLayout.setOrientation(LinearLayout.VERTICAL);
itemLayout.setLayoutParams(itemParam);
waterfall_items.add(itemLayout);
waterfall_container.addView(itemLayout);
}
// 加载所有图片路径
try {
image_filenames = Arrays.asList(assetManager.list(image_path));
} catch (IOException e) {
e.printStackTrace();
}
// 第一次加载
AddItemToContainer(current_page, page_count);
}
private void AddItemToContainer(int pageindex, int pagecount) {
int j = 0;
int imagecount = image_filenames.size();
for (int i = pageindex * pagecount; i < pagecount * (pageindex + 1)
&& i < imagecount; i++) {
j = (j >= column_count) ? 0 : j;
AddImage(image_filenames.get(i), j++);
}
}
private void AddImage(String filename, int columnIndex) {
ImageView imageView = (ImageView) LayoutInflater.from(this).inflate(
R.layout.waterfallitem, null);
waterfall_items.get(columnIndex).addView(imageView);
TaskParam param = new TaskParam();
param.setAssetManager(assetManager);
param.setFilename(image_path + "/" + filename);
param.setItemWidth(itemWidth);
ImageLoaderTask task = new ImageLoaderTask(imageView);
task.execute(param);
}
}
第40,42行,首先获取手机屏幕宽度,然后根据列数获取每一列的宽度,43行获取一个AssetManager对象,用于从assets文件夹中获取图片资源(注:本例中所有图片均存放于本地assets目录下)
第51行设置一个滚动监听,68行是当手机滑动到最低端的时候回调.84,84行,创建column_count个的LinearLayout对象,并设置其宽度为itemWidth,并添加到集合waterfall_items中,同时添加到waterfall_container容器当中.
第90行,获取所有图片的地址.98行,每次加载显示column_count个图片.
第117行,使用ImageLoaderTask异步加载的方式加载图片.
TaskParam.class
package com.sg7.waterfallflow;
import android.content.res.AssetManager;
/**
* 任务参数
*/
public class TaskParam {
private String filename;
private AssetManager assetManager;
private int ItemWidth;
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public AssetManager getAssetManager() {
return assetManager;
}
public void setAssetManager(AssetManager assetManager) {
this.assetManager = assetManager;
}
public int getItemWidth() {
return ItemWidth;
}
public void setItemWidth(int itemWidth) {
ItemWidth = itemWidth;
}
}
这个类不多讲了,存放请求参数的
ImageLoaderTask.class
package com.sg7.waterfallflow;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
public class ImageLoaderTask extends AsyncTask<TaskParam, Void, Bitmap> {
private TaskParam param;
/**
* 弱引用,防止内存溢出
*/
private final WeakReference<ImageView> imageViewReference;
public ImageLoaderTask(ImageView imageView) {
imageViewReference = new WeakReference<>(imageView);
}
@Override
protected Bitmap doInBackground(TaskParam... params) {
param = params[0];
return loadImageFile(param.getFilename(), param.getAssetManager());
}
private Bitmap loadImageFile(final String filename, final AssetManager manager) {
try {
Bitmap bmp = BitmapCache.getInstance().getBitmap(filename, manager);
return bmp;
} catch (Exception e) {
Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
ImageView imageView = imageViewReference.get();
if (imageView != null) {
if (bitmap != null) {
// 获取真实宽高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
LayoutParams lp = imageView.getLayoutParams();
// 调整高度
lp.height = (height * param.getItemWidth()) / width;
imageView.setLayoutParams(lp);
imageView.setImageBitmap(bitmap);
}
}
}
}
ImageLoaderTask继承AsyncTask,这是一个异步任务,主要耗时处理在27行,在58行将图片显示在ImageView上,完全了使命.
第21行,对ImageView使用弱引用,以防止内在溢出
第32行,使用BitmapCache来加载图片,并对图片进行了缓存
BitmapCache.class
package com.sg7.waterfallflow;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;
/**
* 防止溢出
*/
public class BitmapCache {
/**
* 用于Chche内容的存储
*/
private Hashtable<String, BtimapRef> bitmapRefs;
/**
* 垃圾Reference的队列(所引用的对象已经被回收,则将该引用存入队列中)
*/
private ReferenceQueue<Bitmap> queue;
private static BitmapCache cache;
/**
* 取得缓存器实例
*/
public static BitmapCache getInstance() {
if (cache == null) {
cache = new BitmapCache();
}
return cache;
}
private BitmapCache() {
bitmapRefs = new Hashtable<>();
queue = new ReferenceQueue<>();
}
/**
* 继承SoftReference,使得每一个实例都具有可识别的标识。
*/
private class BtimapRef extends SoftReference<Bitmap> {
private String key = "";
public BtimapRef(Bitmap bmp, ReferenceQueue<Bitmap> queue, String key) {
super(bmp, queue);
this.key = key;
}
}
/**
* 依据所指定的文件名获取图片
*/
public Bitmap getBitmap(String filename, AssetManager assetManager) {
Bitmap bitmapImage = null;
// 缓存中是否有该Bitmap实例的软引用,如果有,从软引用中取得。
if (bitmapRefs.containsKey(filename)) {
BtimapRef ref = bitmapRefs.get(filename);
bitmapImage = ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,并保存对这个新建实例的软引用
if (bitmapImage == null) {
BufferedInputStream buf;
try {
buf = new BufferedInputStream(assetManager.open(filename));
bitmapImage = BitmapFactory.decodeStream(buf);
this.addCacheBitmap(bitmapImage, filename);
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmapImage;
}
/**
* 以软引用的方式对一个Bitmap对象的实例进行引用并保存该引用
*/
private void addCacheBitmap(Bitmap bmp, String key) {
cleanCache();
BtimapRef ref = new BtimapRef(bmp, queue, key);
bitmapRefs.put(key, ref);
}
/**
* 清除垃圾引用
*/
private void cleanCache() {
BtimapRef ref = null;
while ((ref = (BtimapRef) queue.poll()) != null) {
bitmapRefs.remove(ref.key);
}
}
}
这个图片加载类,直接看注释
最后说说这个懒加载类LazyScrollView
LazyScrollView.class
package com.sg7.waterfallflow;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
/**
* 懒加载ScrollView
*/
public class LazyScrollView extends ScrollView {
private static Handler handler;
private View view;
public LazyScrollView(Context context) {
super(context);
}
public LazyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LazyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
/**
* getScrollY():已经滚动到屏幕上面的长度
* getHeight() : 此控件在布局中的长度,一个固定值
* view.getMeasuredHeight() : 子控件的长度值
*/
if (view.getMeasuredHeight() <= getScrollY() + getHeight()) {
if (onScrollListener != null) {
onScrollListener.onBottom();
Log.i("aaaa", getScrollY() + ":" + view.getMeasuredHeight() + ":" + getHeight());
/**
* 12-16 23:35:57.571 9780-9780/? I/aaaa: 1030:2254:1230
* 12-16 23:36:37.161 9780-9780/? I/aaaa: 2784:4014:1230
* 12-16 23:37:10.211 9780-9780/? I/aaaa: 4623:5849:1230
*/
}
} else if (getScrollY() == 0) {
//滚动到最顶端
if (onScrollListener != null) {
onScrollListener.onTop();
}
} else {
if (onScrollListener != null) {
onScrollListener.onScroll();
}
}
break;
default:
break;
}
}
}
private void init() {
//给自己设置一个ontouch监听
this.setOnTouchListener(onTouchListener);
handler = new MyHandler();
}
/**
* 触摸监听
*/
OnTouchListener onTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
if (view != null && onScrollListener != null) {
handler.sendMessageDelayed(handler.obtainMessage(1), 200);
}
break;
default:
break;
}
return false;
}
};
/**
* 获得参考的View,主要是为了获得它的MeasuredHeight,然后和滚动条的ScrollY+getHeight作比较。
*/
public void getView() {
this.view = getChildAt(0);
if (view != null) {
init();
}
}
/**
* 定义接口
*
* @author admin
*/
public interface OnScrollListener {
void onBottom();
void onTop();
void onScroll();
}
private OnScrollListener onScrollListener;
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
}
继承于ScrollView类,说明是一个自定义控件
第76行,给本身设置了onTouchListener监听,91行,每一次抬起手指的时候都会通过handler延时200ms发送一个消息给handler去处理,其中处理逻辑在39-66行.其中注释也已经比较清楚了,大家可以自己看一看.
好,瀑布流的示例代码分析到此结束.
源码下载(AndroidStudio项目):http://download.csdn.net/detail/lingwu7/9366161