首页 > 代码库 > Android开发--瀑布流效果的实现

Android开发--瀑布流效果的实现

     对手机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);
	}
}


Android开发--瀑布流效果的实现