对手机App的瀑布流效果一直有所耳闻,却从未自己亲自动手实践,趁着这几天还有些时间,做了些研究,也参考了网络上很多大神的博客,终于写出来自己的瀑布流效果了,先上一图。
正如图所示:瀑布流的原理很简单,就是自己重新写一个ScrollView,添加一个横向排布的LinearLayout,再向这个横向的LinearLayout中添加三个纵向排布的LinearLayout,接着我们就可以向每个一LinearLayout中依次添加图片。原理很容易理解,但实践起来也很困难,需要注意的问题也很多:
1.为了防止OOM,我们应当对那些不可见的图片进行回收,在这里我的思路是:如果以一个屏幕的高度为一页的话,只在当前的程序中缓存下来三页数量的图片,即当前页,前一页和后一页,这样可以提高用户体验。为了回收图片,我们就需要重写一个View来显示瀑布流中的每一图片,异步的去加载Bitmap和回收Bitmap。
2.在ScrollView滑动的过程中监听图片可见性的变化,在这里用两个数组来记录三页中每一个LinearLayout的最上端和最低端的子View的id。
3.利用一个Lrucache来加快图片的响应时间。
下面是我的代码,如果各位大神发现错误,还请多多指教。
WaterFallView瀑布流显示整体布局类
package com.example.waterfalltest.widget;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
import com.example.waterfalltest.widget.WaterFallItem.LoadCompletedListener;
/**
*
* 需调用setup方法首先对WaterFallView进行配置初始化
* 根布局为一个横向的LinearLayout,然后根据设置的列数依次添加纵向的LinearLayout,每次将图片加进纵向LinearLayout中
* 如果以一个屏幕的高度为一页的话,只在当前的程序中缓存下来三页数量的图片
* @author acer
*
*/
public class WaterFallView extends ScrollView {
private static final String TAG = "WaterFallView";
private LinearLayout mContainerLayout;
private ArrayList<LinearLayout> mLayoutsList;
private final static int PAGE_COUNT = 20;
private int currentPage = 0;
private int colNum;
private int colWidth;
private int colHeight[];
private List<String> mImageUrl;
private int screenHeight;
private int[] topIndexArray;//记录三页中顶部Item在LinearLayout的Id
private int[] bottomIndexArray;//记录三页中底部Item在LinearLayout的Id
private int[] lastBottomIndexArray;//所有Item中的最低部
public WaterFallView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}
public WaterFallView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}
public WaterFallView(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}
private void init() {
mContainerLayout = new LinearLayout(getContext());
mContainerLayout.setBackgroundColor(Color.WHITE);
mContainerLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
addView(mContainerLayout, layoutParams);
mLayoutsList = new ArrayList<LinearLayout>();
screenHeight = getResources().getDisplayMetrics().heightPixels;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (getScrollY() + getHeight() >= computeVerticalScrollRange()) {// 已经加载到底部
loadOnpage();
return;
}
if (t > oldt) {// 向下滑动
for (int i = 0; i < colNum; i++) {
LinearLayout layout = mLayoutsList.get(i);
WaterFallItem topItem = (WaterFallItem) layout
.getChildAt(topIndexArray[i]);
if (topItem.getPositionY() < getScrollY() - screenHeight) {
topItem.recycle();
topIndexArray[i]++;
}
WaterFallItem bomItem = (WaterFallItem) layout
.getChildAt(Math.min(bottomIndexArray[i] - 1, lastBottomIndexArray[i]));
if (bomItem.getPositionY() <= getScrollY() + 2 * screenHeight) {
bomItem.reloadBitmap();
bottomIndexArray[i] = Math.min(
bottomIndexArray[i] + 1, lastBottomIndexArray[i]);
}
}
} else {// 向上滑动
for (int i = 0; i < colNum; i++) {
LinearLayout layout = mLayoutsList.get(i);
WaterFallItem bomItem = (WaterFallItem) layout
.getChildAt(bottomIndexArray[i] - 1);
if (bomItem.getPositionY() > getScrollY() + 2 * screenHeight) {
bomItem.recycle();
bottomIndexArray[i]--;
}
WaterFallItem topItem = (WaterFallItem) layout
.getChildAt(Math.max(topIndexArray[i] - 1, 0));
if (topItem.getPositionY() >= getScrollY() - screenHeight) {
topItem.reloadBitmap();
topIndexArray[i] = Math.max(
topIndexArray[i] - 1, 0);
}
}
}
}
public void setUp(List<String> imgUrl, int col) {
colNum = col;
colWidth = getResources().getDisplayMetrics().widthPixels / col;
mImageUrl = imgUrl;
colHeight = new int[col];
bottomIndexArray = new int[col];
topIndexArray = new int[col];
lastBottomIndexArray = new int[col];
for (int i = 0; i < col; i++) {
LinearLayout.LayoutParams colLayoutParams = new LinearLayout.LayoutParams(
colWidth, LinearLayout.LayoutParams.WRAP_CONTENT);
LinearLayout layout = new LinearLayout(getContext());
layout.setOrientation(LinearLayout.VERTICAL);
mContainerLayout.addView(layout, colLayoutParams);
mLayoutsList.add(layout);
}
loadOnpage();
}
private void loadOnpage() {
int end;
if (mImageUrl.size() > (currentPage + 1) * PAGE_COUNT) {
end = (currentPage + 1) * PAGE_COUNT;
} else {
end = mImageUrl.size();
}
for (int i = currentPage * PAGE_COUNT; i < end; i++) {
WaterFallItem item = new WaterFallItem(getContext(), colWidth - 10);
item.setId(i);
item.loadBitmap("images/" + mImageUrl.get(i));
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
WaterFallItem item = (WaterFallItem) arg0;
Toast.makeText(getContext(), "click:" + item.getId(),
Toast.LENGTH_SHORT).show();
}
});
final int layoutId = i % colNum;
item.setLoadCompletedListener(new LoadCompletedListener() {
@Override
public void completed(WaterFallItem waterFallItem, int height) {
// TODO Auto-generated method stub
LinearLayout layout = mLayoutsList.get(layoutId);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(5, 10, 5, 0);
layout.addView(waterFallItem, layoutParams);
colHeight[layoutId] += (height + 10);
waterFallItem.setPositionY(colHeight[layoutId]);
lastBottomIndexArray[layoutId]++;
if (currentPage == 1
&& waterFallItem.getPositionY() < 2 * screenHeight) {
bottomIndexArray[layoutId]++;
}
}
});
}
currentPage++;
}
}
WaterFallItem类,用来回收和加载图片,即图中的Item
package com.example.waterfalltest.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.view.View;
/**
*
* 瀑布流中的Item自定义View
* 负责Bitmap的加载以及回收工作,并记录下所需的数据
* @author acer
*
*/
public class WaterFallItem extends View {
private static final String TAG = "WaterFallItem";
private int id;
private int height;//View的高度
private int width;//View的宽度
private int positionY;//View中底部距离整个ScrollView最顶部的距离
private Paint mPaint;
private Rect dstRect;
private boolean isFirstLoad = true;
private String mUrlString;
private Bitmap mBitmap;
private LoadCompletedListener loadCompletedListener;
public WaterFallItem(Context context, int width) {
super(context);
// TODO Auto-generated constructor stub
this.width = width;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
}
public void loadBitmap(String url){
this.mUrlString = url;
new LoadBitmapTask().execute(url);
}
public void reloadBitmap(){
isFirstLoad = false;
loadBitmap(mUrlString);
}
/**
* 防止OOM进行回收
*/
public void recycle() {
if (mBitmap == null || mBitmap.isRecycled())
return;
new Thread(new Runnable() {
@Override
public void run() {
if (mBitmap != null) {
mBitmap.recycle();
GetBitmapUtils.removeBitmap(mUrlString);
mBitmap = null;
}
}
}).start();
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, null, dstRect, mPaint);
}
else if(dstRect != null) {
canvas.drawRect(dstRect, mPaint);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(width, height);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getmHeight(){
return height;
}
public void setLoadCompletedListener(LoadCompletedListener loadCompletedListener) {
this.loadCompletedListener = loadCompletedListener;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
private class LoadBitmapTask extends AsyncTask<String, Void, Void>{
@Override
protected Void doInBackground(String... arg0) {
// TODO Auto-generated method stub
String url = arg0[0];
mBitmap = GetBitmapUtils.getBitmap(getContext(), url);
if (mBitmap != null) {
int bmpWidth = mBitmap.getWidth();
int bmpHeight = mBitmap.getHeight();
height = (int) (bmpHeight * width / bmpWidth);
}
dstRect = new Rect(0, 0, width, height);
return null;
}
@Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
if (mBitmap != null) {
WaterFallItem.this.invalidate();
if (isFirstLoad && loadCompletedListener != null) {
loadCompletedListener.completed(WaterFallItem.this, height);
}
}
}
}
public interface LoadCompletedListener {
public void completed(WaterFallItem waterFallItem, int height);
}
}
GetBitmapUtils,加载Bitmap工具类package com.example.waterfalltest.widget;
import java.io.IOException;
import java.io.InputStream;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
/**
* 加载Bitmap的工具类
* @author acer
*
*/
public class GetBitmapUtils {
private static LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(
60);
public static Bitmap getBitmap(Context context, String url) {
Bitmap bitmap = mLruCache.get(url);
if (bitmap == null) {//可以在这里换成网络加载
InputStream inStream = null;
try {
inStream = context.getAssets().open(url);
bitmap = BitmapFactory.decodeStream(inStream);
inStream.close();
inStream = null;
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null) {
mLruCache.put(url, bitmap);
}
}
return bitmap;
}
public static void removeBitmap(String url) {
mLruCache.remove(url);
}
}
原文地址:http://blog.csdn.net/smbroe/article/details/43021595