这是一个Android瀑布流的实现demo。
瀑布流我的实现是定义三个linearlayout,然后向里面addView(),如果多了会出现oom异常,所以做了一些处理。
1.lrucache缓存
2.只显示当前屏的图片
3.滑动过程中不加载图片
4.大图缩放成小图
直接看代码:
PhotoFallScrollView.java主类 自定义的ScrollView.
package com.pangzaifei.falls;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
/**
* 瀑布流类
*
* 原理: 1:创建3个linearlayout,设置他们的宽度,将获得的图片压缩成和3个linearlayout一样的宽度,
* 然后根据3个linearlayout的高度来判断,将bitmap添加到哪一个linearlayout中
* 2:翻页处理,根据手势抬起的位置和滑动的末尾处来进行翻页
*
* @author pangzf
* @date 2014年7月15日 上午10:33:05
*/
public class PhotoFallScrollView extends ScrollView implements OnTouchListener {
/**
* 页数
*/
private static int page;
/**
* 每页显示多少张
*/
private static final int PAGE_SIZE = 8;
private Context mContext;
/**
* 数据源图片
*/
private Images mImagesThoumb;
/**
* task请求集合
*/
private Set<DownLoadTask> mTasks;
boolean isFirstEntr = true;
private LinearLayout mFirstColumn;
private LinearLayout mSecondColumn;
private LinearLayout mThirdColumn;
private int mFirstColumnHeight;
private int mSecondColumnHeight;
private int mThirdColumnHeight;
private int mClolumnWidth;
private long mDelay = 5;
/**
* 上次滑动的最后位置
*/
private static int lastScrollY = -1;
/**
* 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
*/
private boolean loadOnce;
/**
* 存放图片的集合
*/
private List<ImageView> mImageViewList = new ArrayList<ImageView>();
public PhotoFallScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mContext = context;
init();
}
public PhotoFallScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
public PhotoFallScrollView(Context context) {
super(context);
this.mContext = context;
init();
}
/**
* 初始化
*/
private void init() {
mImagesThoumb = Images.getInstance();
mTasks = new HashSet<DownLoadTask>();
setOnTouchListener(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 第一次进入就加载第一页的图片
if (changed && !loadOnce) {
mScrollViewHeight = this.getHeight();
mScrollLayout = this.getChildAt(0);
mFirstColumn = (LinearLayout) findViewById(R.id.first_column);
mSecondColumn = (LinearLayout) findViewById(R.id.second_column);
mThirdColumn = (LinearLayout) findViewById(R.id.third_column);
mClolumnWidth = mFirstColumn.getWidth();
loadOnce = true;
loadMoreImages();
}
}
/**
* 加载图片
*/
private void loadMoreImages() {
if (hashSdcard()) {
// 根据页数加载图片
int startIndex = page * PAGE_SIZE;
int endIndex = page * PAGE_SIZE + PAGE_SIZE;
if (startIndex < mImagesThoumb.imageThumbs.length) {
if (endIndex > mImagesThoumb.imageThumbs.length) {
endIndex = mImagesThoumb.imageThumbs.length;
}
for (int i = startIndex; i < endIndex; i++) {
String imageUrl = mImagesThoumb.imageThumbs[i].toString();
if (imageUrl != null && !"".equals(imageUrl)) {
downLoadData(imageUrl);
}
}
page++;
} else {
Toast.makeText(mContext, "没有更多图片了", 0).show();
}
} else {
Toast.makeText(mContext, "无sdcard", 0).show();
}
}
/**
* 下载
*
* @param imageUrl
*/
private void downLoadData(String imageUrl) {
DownLoadTask task = new DownLoadTask();
mTasks.add(task);
task.execute(imageUrl);
}
public class DownLoadTask extends AsyncTask<String, String, Bitmap> {
private String mImageUrl;
@Override
protected Bitmap doInBackground(String... params) {
try {
mImageUrl = params[0];
Bitmap bitmapFromMemory = mImagesThoumb
.getMemoryCache(mImageUrl);
if (bitmapFromMemory != null) {
return bitmapFromMemory;
}
if (hashSdcard()) {
Bitmap bitmap = loadImage(mImageUrl);
return bitmap;
} else {
Toast.makeText(mContext, "无sdcard,无法获取图片", 0).show();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 展示图片
if (bitmap != null) {
// 1.缩放图片
// 2.新建ImageView
// 3.找到需要的linerlayout添加imageView
float width = bitmap.getWidth();
float radio = width / mFirstColumn.getWidth();
float scaleHeight = bitmap.getHeight() / radio;
addImage(bitmap, mFirstColumn.getWidth(), scaleHeight);
}
mTasks.remove(this);
}
/**
* 将图片添加到linearlayout中
*
* @param bitmap
* @param scaleHeight
*/
public void addImage(Bitmap bitmap, float width, float scaleHeight) {
// 生成缩放的iv
ImageView iv = new ImageView(mContext);
android.view.ViewGroup.LayoutParams params = new LayoutParams(
(int) width, (int) scaleHeight);
iv.setLayoutParams(params);
if (bitmap != null) {
// 解决默认图片有大有小的问题
iv.setScaleType(ScaleType.FIT_XY);
iv.setPadding(5, 5, 5, 5);
iv.setImageBitmap(bitmap);
iv.setTag(R.string.iamgurl, mImageUrl);
findColumnToAdd(iv, (int) scaleHeight).addView(iv);
mImageViewList.add(iv);
}
}
}
private Bitmap downLoad(String imageUrl) throws IOException {
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
HttpURLConnection conn = null;
File imageFile = null;
try {
URL url = new URL(imageUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000);
conn.setConnectTimeout(5000);
conn.setDoInput(true);
conn.setDoOutput(true);
InputStream is = conn.getInputStream();
imageFile = new File(getImagePath(imageUrl));
bis = new BufferedInputStream(is);
fos = new FileOutputStream(imageFile);
bos = new BufferedOutputStream(fos);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
if (conn != null) {
conn.disconnect();
}
}
// 如果imageFile不为null,将图片添加到memory中
if (imageFile != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getPath());
mImagesThoumb.addBitmapToMemoryCache(imageUrl, bitmap);
return bitmap;
}
return null;
}
/**
* 判断图片sdcard是否有图片,如果有就用,没有就下载
*
* @param mImageUrl
* @return
*/
public Bitmap loadImage(String mImageUrl) throws Exception {
File file = new File(getImagePath(mImageUrl));
if (!file.exists()) {
downLoad(mImageUrl);
}
if (mImageUrl != null) {
// 处理本地图片,设置大小防止oom
Bitmap bitmap = mImagesThoumb.decodeSimpleBitMapFromResource(
file.getPath(), mClolumnWidth);
// Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
if (bitmap != null) {
mImagesThoumb.addBitmapToMemoryCache(mImageUrl, bitmap);
return bitmap;
}
}
return null;
}
/**
* 查找要添加的column
*
* @param iv
*/
private LinearLayout findColumnToAdd(ImageView iv, int imageHeight) {
if (mFirstColumnHeight <= mSecondColumnHeight) {
if (mFirstColumnHeight <= mThirdColumnHeight) {
iv.setTag(R.string.border_top, mFirstColumnHeight);
mFirstColumnHeight += imageHeight;
iv.setTag(R.string.border_bottom, mFirstColumnHeight);
return mFirstColumn;
}
iv.setTag(R.string.border_top, mThirdColumnHeight);
mThirdColumnHeight += imageHeight;
iv.setTag(R.string.border_bottom, mThirdColumnHeight);
return mThirdColumn;
} else {
if (mSecondColumnHeight <= mThirdColumnHeight) {
iv.setTag(R.string.border_top, mSecondColumnHeight);
mSecondColumnHeight += imageHeight;
iv.setTag(R.string.border_bottom, mSecondColumnHeight);
return mSecondColumn;
}
iv.setTag(R.string.border_top, mThirdColumnHeight);
mThirdColumnHeight += imageHeight;
iv.setTag(R.string.border_bottom, mThirdColumnHeight);
return mThirdColumn;
}
}
/**
* 获得file地址
*
* @param imageUrl
* @return
*/
private String getImagePath(String imageUrl) {
int lastIndexOf = imageUrl.lastIndexOf("/");
String imageName = imageUrl.substring(lastIndexOf + 1);
String imageDir = Environment.getExternalStorageDirectory().getPath()
+ "/pangzaifei/";
File file = new File(imageDir);
if (!file.exists()) {
file.mkdir();
}
String imagePath = imageDir + imageName;
return imagePath;
}
/**
* 获得图片的名字
*
* @param imageUrl
*/
private boolean hashSdcard() {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return true;
}
return false;
}
@Override
/**
* 当手势抬起时,开始每个5毫秒计算位置
*/
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// 发送handler
Message msg = mHandler.obtainMessage();
msg.obj = this;
mHandler.sendMessageDelayed(msg, mDelay);
}
return false;
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 判断是否已经滑到了最低处,如果滑到了最低处,则加载更多页面,否则继续发送handler扫描
PhotoFallScrollView scrollView = (PhotoFallScrollView) msg.obj;
int scrollY = scrollView.getScrollY();
if (scrollY == lastScrollY) {
if (mScrollViewHeight + scrollY >= mScrollLayout.getHeight()
&& mTasks.isEmpty()) {
scrollView.loadMoreImages();
}
scrollView.checkVisibile();
} else {
lastScrollY = scrollY;
Message message = new Message();
message.obj = scrollView;
mHandler.sendMessageDelayed(message, mDelay);
}
}
};
private int mScrollViewHeight;
private View mScrollLayout;
/**
* 想不可见的变为空图片
*/
protected void checkVisibile() {
if (mImageViewList != null && mImageViewList.size() > 0) {
for (int i = 0; i < mImageViewList.size(); i++) {
ImageView iv = mImageViewList.get(i);
int borderTop = (int) iv.getTag(R.string.border_top);
int borderBottom = (int) iv.getTag(R.string.border_bottom);
if (borderBottom > getScrollY()
&& borderTop < getScrollY() + mScrollViewHeight) {
String imageUrl = (String) iv.getTag(R.string.iamgurl);
if (imageUrl != null && !"".equals(imageUrl)) {
Bitmap bitmap = mImagesThoumb.getMemoryCache(imageUrl);
if (bitmap != null) {
iv.setImageBitmap(bitmap);
} else {
downLoadData(imageUrl);
}
}
} else {
iv.setImageResource(R.drawable.empty_photo);
}
}
}
}
}
上面是主要的东西,思路和注释已经添加。其中imageView中的tag我要解释一下,主要保存了每一列的上边距和下边距和图片的url,这个方法,就是只显示当前页的图片。
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.pangzaifei.falls.PhotoFallScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<LinearLayout
android:id="@+id/first_column"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
</LinearLayout>
<LinearLayout
android:id="@+id/second_column"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
</LinearLayout>
<LinearLayout
android:id="@+id/third_column"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
</com.pangzaifei.falls.PhotoFallScrollView>
</LinearLayout>
效果图: