利用LruCache加载网络图片实现图片瀑布流效果(基础版)

PS:
2015年1月20日21:37:27
关于LoadImageAsyncTask和checkAllImageViewVisibility可能有点小bug
修改后的代码请参见升级版本的代码
http://blog.csdn.net/lfdfhl/article/details/42925193


MainActivity如下:

package cc.patience3;

import android.os.Bundle;
import android.app.Activity;
/**
 * Demo描述:
 * 采用瀑布流的形式加载大量网络图片
 * 详细分析参见WaterfallScrollView
 * 
 * 参考资料:
 * 1 http://blog.csdn.net/guolin_blog/article/details/10470797
 * 2 http://blog.csdn.net/lfdfhl/article/details/18350601
 *   Thank you very much
 *   
 */
public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

}


效果图如下:


WaterfallScrollView如下:

package cc.patience3;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.AsyncTask;
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;
/**
 * Demo功能:
 * 加载网络图片实现图片瀑布流效果(参见截图)
 * 
 * Demo流程:
 * 1 为了加载的众多图片可以在屏幕上滑动显示,所以需要一个ScrollView控件.
 *   于是自定义ScrollView
 * 2 将自定义ScrollView作为布局xml文件的根布局.
 *   在根布局下有一个LinearLayout它就是该自定义ScrollView的第一个子孩子.
 *   即代码中waterfallScrollView.getChildAt(0)
 *   将该LinearLayout均分成三个子LinearLayout,它们三的宽度平分屏幕的宽度.
 *   这样我们就可以往这三个LinearLayout中不断添加图片,形成瀑布流
 * 3 将网络图片添加到瀑布流的过程
 *   3.1 当手指在屏幕上停止滑动时(ACTION_UP)加载图片
 *   3.2 从网络中下载图片
 *   3.3 找到三个LinearLayout中当前高度最小的,将图片添加进去
 *   3.4 在添加图片后对ScrollView中所有ImageView进行检查.
 *       对于不在屏幕上显示的ImageView将其所加载的网络图片替换成本地一张小图片.
 * 4 为了加载速度和内存的有效使用,示例中采用了LruCache.
 * 
 * 
 * 错误总结:
 * 在使用ImageView.setTag(key, tag)看到第一个参数为int,于是为其指定一个final的int
 * 运行报错:
 * java.lang.IllegalArgumentException: The key must be an application-specific resource id.  
 * 原因是不可自己指定该值,而应该使用系统指定的int值.这么做大概是为了防止自己指定的值与系统某个值冲突吧.
 * 解决办法:在Strings.xml中指定值string值然后使用其在R文件中的int值即可,例如:
 * imageView.setTag(R.string.IMAGE_URL_TAG, imageUrl);其中:
 * R.string.IMAGE_URL_TAG就是字符串IMAGE_URL_TAG在R文件中的int值
 * 
 * 在此可见setTag方法的用途:为某个View保存数据.
 * 该方法还是挺有用的,可以把属于该View的某些属性保存到该View里面,而不用单独找个地方来存这些数据
 *
 */
public class WaterfallScrollView extends ScrollView implements OnTouchListener {
	// 每页加载的图片数量
	public final int PAGE_SIZE = 15;
	// 当前页码
	private int currentPage;
	// 每一列的宽度
	private int everyColumnWidth;
	// 第一列的高度
	private int firstColumnHeight;
	// 第一列的布局
	private LinearLayout mFirstLinearLayout;
	// 第二列的高度
	private int secondColumnHeight;
	// 第二列的布局
	private LinearLayout mSecondLinearLayout;
	// 第三列的高度
	private int thirdColumnHeight;
	// 第三列的布局
	private LinearLayout mThirdLinearLayout;
	// 是否已经进入该界面
	private boolean isFirstEnterThisScrollView = false;
	// LruCache
	private LruCacheImageLoader mLruCacheImageLoader;
	// 记录所有正在下载或等待下载的异步任务
	private HashSet<LoadImageAsyncTask> mLoadImageAsyncTaskHashSet;
	// 记录ScrollView中的所有ImageView
	private ArrayList<ImageView> mAllImageViewArrayList;
	// 该WaterfallScrollView控件的高度
	private int waterfallScrollViewHeight;
	// ScrollView顶端已经向上滑出屏幕长度
	private int scrollY=0;
	private int lastScrollY=-1;
	// 处理消息的Handle
	private Handler mHandler;
	// Context
	private Context mContext;
	private final int REFRESH=9527;
	public WaterfallScrollView(Context context) {
		super(context);
		init(context);
	}

	public WaterfallScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public WaterfallScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}
	
	/**
	 * 判断scrollView是否滑动到底部的三个值:
	 * scrollY:ScrollView顶端已经滑出去的高度 
	 * waterfallScrollViewHeight:ScrollView的布局高度
	 * scrollView.getChildAt(0).getMeasuredHeight():ScrollView内容的高度.
	 * 常常有一部分内容要滑动后才可见,这部分的高度也包含在了这里面
	 */
	private void init(Context context){
		mContext=context;
		this.setOnTouchListener(this);
		mAllImageViewArrayList=new ArrayList<ImageView>();
		mLoadImageAsyncTaskHashSet=new HashSet<LoadImageAsyncTask>();
		mLruCacheImageLoader=LruCacheImageLoader.getLruCacheImageLoaderInstance();
		mHandler=new Handler(){
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				if (msg.what==9527) {
					WaterfallScrollView waterfallScrollView=(WaterfallScrollView) msg.obj;
					scrollY=waterfallScrollView.getScrollY();
					// 如果当前的滚动位置和上次相同,表示已停止滚动
					if (lastScrollY==scrollY) {
						// 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
						int scrollViewMeasuredHeight=waterfallScrollView.getChildAt(0).getMeasuredHeight();
						boolean isAsyncTashHashSetEmpty=mLoadImageAsyncTaskHashSet.isEmpty();
						if (waterfallScrollViewHeight+scrollY>=scrollViewMeasuredHeight&&isAsyncTashHashSetEmpty) {
							waterfallScrollView.loadNextPageImages();
						}
						//检查所有ImageView的可见性
						checkAllImageViewVisibility();
					} else {
						lastScrollY=scrollY;
						Message message=new Message();
						message.what=REFRESH;
						message.obj=WaterfallScrollView.this;
						// 5毫秒后再次对滚动位置进行判断
						mHandler.sendMessageDelayed(message, 5);
					}
				}
			}
		};
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (!isFirstEnterThisScrollView) {
			isFirstEnterThisScrollView=true;
			waterfallScrollViewHeight=getHeight();
			mFirstLinearLayout=(LinearLayout) findViewById(R.id.firstLinearLayout);
			mSecondLinearLayout=(LinearLayout) findViewById(R.id.secondLinearLayout);
			mThirdLinearLayout=(LinearLayout) findViewById(R.id.thirdLinearLayout);
			everyColumnWidth=mFirstLinearLayout.getWidth();
			loadNextPageImages();
		}
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
	}

	/**
	 * 这里对于手指抬起时(ACTION_UP)时,监听ScrollView是否已经停止滚动的判断的思路不错.
	 * 在ACTION_UP时直接用Handler发送一个消息在handleMessage中处理判断,如果此时还
	 * 没有停止滚动,则延时一定时间再次发送消息判断滚动是否停止.
	 * 这样做避免的在ACTION_UP时去加载图片而是在ScrollView停止滚动时去加载.
	 */
	@Override
	public boolean onTouch(View view, MotionEvent motionEvent) {
		if (motionEvent.getAction()==MotionEvent.ACTION_UP) {
			Message message=new Message();
			message.obj=this;
			message.what=REFRESH;
			mHandler.sendMessageDelayed(message, 5);
		}
		return false;
	}
	
	private void loadNextPageImages(){
		if (Utils.isExistSDCard()) {
			int start=PAGE_SIZE*currentPage;
			int end=PAGE_SIZE*currentPage+PAGE_SIZE;
			LoadImageAsyncTask loadImageAsyncTask;
			if (start<ImagesUrl.urlStringArray.length) {
				if (end>ImagesUrl.urlStringArray.length) {
					end=ImagesUrl.urlStringArray.length;
				}
				Toast.makeText(mContext, "开始加载", Toast.LENGTH_SHORT).show();
				for (int i = start;i < end; i++) {
					loadImageAsyncTask=new LoadImageAsyncTask();
					loadImageAsyncTask.execute(ImagesUrl.urlStringArray[i]);
					mLoadImageAsyncTaskHashSet.add(loadImageAsyncTask);
				}
				currentPage++;
			} else {
				
			}
			
		} else {
			Toast.makeText(mContext, "无SD卡", Toast.LENGTH_LONG).show();
		}
	}
	
	/**
	 * 判断ImageView是否可见
	 * 如果可见:
	 * 1  从LruCache取出图片显示
	 * 2 若不在LruCache中,则开启异步任务下载
	 * 若不可见:
	 * 将ImageView显示的图片替换成本地图片
	 */
	private void checkAllImageViewVisibility(){
		ImageView imageView=null;
		for(int i=0;i<mAllImageViewArrayList.size();i++){
			imageView=mAllImageViewArrayList.get(i);
			int top_border=(Integer) imageView.getTag(R.string.TOP_BORDER_TAG);
			int bottom_border=(Integer) imageView.getTag(R.string.BOTTOM_BORDER_TAG);
			if (bottom_border > getScrollY() && top_border < getScrollY() + waterfallScrollViewHeight) {
				String imageUrl=(String) imageView.getTag(R.string.IMAGE_URL_TAG);
				Bitmap bitmap=mLruCacheImageLoader.getBitmapFromLruCache(imageUrl);
				if (bitmap==null) {
					LoadImageAsyncTask loadImageAsyncTask=new LoadImageAsyncTask();
					loadImageAsyncTask.execute(imageUrl);
				} else {
					imageView.setImageBitmap(bitmap);
				}
				
			} else {
				imageView.setImageResource(R.drawable.empty_photo);
			}
		}
	}
	
	/**
	 * 该LoadImageAsyncTask是获取网络图片的入口:
	 * 1 从LruCache中获取,取到则停止
	 * 2 若不在LruCache,则从SD卡获取
	 * 3 若在则从SD卡获取
	 * 4 若不在SD卡,则从网络获取且保持至SD卡
	 * 5 从SD卡获取下载的图片
	 * 6 添加到LruCache中
	 * 
	 * 注意不管这个图片是在SD卡还是从网络下载,这都是获取图片的入口,这么做的好处
	 * 1   统一了获取图片的入口.
	 *   如果把获取图片分为图片在LruCache,图片在SD卡,图片在网络上这几种不同
	 *   的情况而去分别用对应的函数获取,这样势必会导致该需求的多入口.凌乱,不好优化.
	 *   而且这几种方式放到AsyncTask中都不会出错,尤其是网络请求耗时的情况下.
	 * 2 不管通过哪种方式获取到了图片,我们都要对图片再次修整,比如缩放.
	 *   我们可以把这些操作又统一放到异步操作的onPostExecute()方法中.
	 */
	private class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{
		private String imageUrl;
		private Bitmap bitmap;
		@Override
		protected Bitmap doInBackground(String... params) {
			imageUrl=params[0];
			bitmap=mLruCacheImageLoader.getBitmapFromLruCache(imageUrl);
			if (bitmap==null) {
				String filePath=Utils.getImageFilePath(imageUrl);
				File imageFile=new File(filePath);
				if (!imageFile.exists()) {
					Utils.getBitmapFromNetWorkAndSaveToSDCard(imageUrl, filePath);
				}
				if (filePath!=null) {
					bitmap=Utils.getBitmapFromSDCard(filePath, everyColumnWidth);
					if (bitmap!=null) {
						mLruCacheImageLoader.addBitmapToLruCache(imageUrl, bitmap);
					}
				}
			} else {

			}
			return bitmap;
		}
		
		/**
		 * 在onPostExecute()对图片进行修整
		 * 因为在doInBackground()的loadImage()方法中已经把经过scale的图片存到了SD卡和LruCache中
		 * 并且在计算inSampleSize的时候是以宽width为标准的.
		 * 比如inSampleSize=2,那么保存的图的宽和高都是原来的二分之一.
		 * 但是请注意inSampleSize是int类型的,那么缩放出来的比例多半不是我们期望的刚好屏幕宽度的三分之一,它是有偏差的.
		 * 所以在这里进行修正,尤其是对高进行修正.
		 * 这样就保证了宽是一个定值(屏幕的三分之一),高也得到了调整,不至于严重失真.
		 * 
		 */
		@Override
		protected void onPostExecute(Bitmap bitmap) {
			super.onPostExecute(bitmap);
			mLoadImageAsyncTaskHashSet.remove(this);
			if (bitmap!=null) {
				double ration=bitmap.getWidth()/(everyColumnWidth*1.0);
				int imageViewHeight=(int) (bitmap.getHeight()/ration);
				int imageViewWidth=everyColumnWidth;
				addImageToScrollView(bitmap,imageViewWidth,imageViewHeight,imageUrl);
			}
		}
	}
	
	/**
	 * 将获取到的Bitmap添加到ImageView中.
	 * 这里利用View.setTag()的方式为该ImageView保存了其相关信息.
	 * 比如该ImageView加载的图片的url,它的上下边在ScrollView中的位置信息等.
	 */
	private void addImageToScrollView(Bitmap bitmap,int imageViewWidth,int imageViewHeight,String imageUrl){
		ImageView imageView=new ImageView(mContext);
		LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(imageViewWidth, imageViewHeight);
		imageView.setImageBitmap(bitmap);
		imageView.setLayoutParams(layoutParams);
		imageView.setScaleType(ScaleType.FIT_XY);
		imageView.setPadding(5, 5, 5, 5);
		imageView.setTag(R.string.IMAGE_URL_TAG, imageUrl);
		addImageToColumn(imageView);
		mAllImageViewArrayList.add(imageView);
	}
	
	
	/**
	 * 找到高度最小的LinearLayout并且将ImageView添加进去
	 */
	private void addImageToColumn(ImageView imageView){
		int imageViewHeight=imageView.getLayoutParams().height;
		if (firstColumnHeight <= secondColumnHeight) {
			if (firstColumnHeight <= thirdColumnHeight) {
				imageView.setTag(R.string.TOP_BORDER_TAG, firstColumnHeight);
				firstColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, firstColumnHeight);
				mFirstLinearLayout.addView(imageView);
			}else{
				imageView.setTag(R.string.TOP_BORDER_TAG, thirdColumnHeight);
				thirdColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, thirdColumnHeight);
				mThirdLinearLayout.addView(imageView);
			}
		} else {
			if (secondColumnHeight <= thirdColumnHeight) {
				imageView.setTag(R.string.TOP_BORDER_TAG, secondColumnHeight);
				secondColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, secondColumnHeight);
				mSecondLinearLayout.addView(imageView);
			}else{
				imageView.setTag(R.string.TOP_BORDER_TAG, thirdColumnHeight);
				thirdColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, thirdColumnHeight);
				mThirdLinearLayout.addView(imageView);
			}
		}
		
	}
}



LruCacheImageLoader如下:
package cc.patience3;

import android.graphics.Bitmap;
import android.util.LruCache;

public class LruCacheImageLoader {
	
	private static LruCacheImageLoader mLruCacheImageLoader;
	
	private static LruCache<String, Bitmap> mLruCache;
	
	private LruCacheImageLoader(){
		int maxMemory=(int) Runtime.getRuntime().maxMemory();
		int size=maxMemory/6;
		//设定LruCache的缓存为可用内存的六分之一
		mLruCache=new LruCache<String, Bitmap>(size){
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				return bitmap.getByteCount();
			}
			
		};
	}
	
	public static LruCacheImageLoader getLruCacheImageLoaderInstance(){
		if (mLruCacheImageLoader==null) {
			mLruCacheImageLoader=new LruCacheImageLoader();
		}
		return mLruCacheImageLoader;
	}
	
	/**
	 * 从LruCache中获取图片,若不存在返回null
	 */
	public static Bitmap getBitmapFromLruCache(String key){
		return mLruCache.get(key);
	}
	
	/**
	 * 往LruCache中添加图片.
	 * 当然要首先判断LruCache中是否已经存在该图片,若不存在再添加
	 */
	public static void addBitmapToLruCache(String key,Bitmap bitmap){
		if (getBitmapFromLruCache(key)==null) {
			mLruCache.put(key, bitmap);
		}
	}

}

Utils如下:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.apache.http.HttpStatus;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Environment;

public class Utils {
	public final static String IMAGES_DIR_NAME="waterfallImages";
	public final static String IMAGES_DIR_PATH=Environment.getExternalStorageDirectory()+File.separator+IMAGES_DIR_NAME;

	/**
	 * 判断SD卡是否存在
	 */
	public static boolean isExistSDCard() {
		boolean isExist = false;
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			isExist = true;
		}
		return isExist;
	}
	
	/**
	 * 从SD卡中获取图片
	 * 
	 * 注意事项:
	 * 这里采用BitmapFactory.decodeFileDescriptor()的方式来避免内存溢出.
	 * 而不是用BitmapFactory.decodeFile()的方式
	 */
	public static Bitmap getBitmapFromSDCard(String filePath,int requestWidth){
		Bitmap bitmap=null;
		try {
			Options options=new Options();
			options.inJustDecodeBounds=true;
			BitmapFactory.decodeFile(filePath, options);
			options.inSampleSize=calculateInSampleSize(options,requestWidth);
			options.inJustDecodeBounds=false;
			FileInputStream fileInputStream=new FileInputStream(filePath);
			FileDescriptor fileDescriptor=fileInputStream.getFD();
			bitmap=BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
			fileInputStream.close();
		} catch (Exception e) {
		
		}
		return bitmap;
	}
	
	public static Bitmap fixBitmap(){
		return null;
	}
	
	/**
	 * 计算图片的缩放比例
	 */
	public static int calculateInSampleSize(Options options,int requestWidth){
		int inSampleSize=1;
		//SD卡中图片的宽
		int outWidth=options.outWidth;
		if (outWidth>requestWidth) {
			inSampleSize=Math.round((float) outWidth / (float) requestWidth);
		 }
		return inSampleSize;
	}
	
	/**
	 * 依据图片的Url获取其在SDCard的存储路径
	 */
	public static String getImageFilePath(String imageUrl){
		File dir=new File(IMAGES_DIR_PATH);
		if (!dir.exists()) {
			dir.mkdirs();
		}
		String imageFilePath=null;
		String imageName=null;
		int start=imageUrl.lastIndexOf("/");
		int end=imageUrl.lastIndexOf(".");
		imageName=imageUrl.substring(start+1, end);
		imageFilePath=IMAGES_DIR_PATH+File.separator+imageName;
		return imageFilePath;
	}
	
	
	/**
	 * 从网络获取图片且保存至SD卡
	 */
	public static void getBitmapFromNetWorkAndSaveToSDCard(String imageUrl,String filePath){
		URL url=null;
		File imageFile=null;
		HttpURLConnection httpURLConnection=null;
		FileOutputStream fileOutputStream=null;
		BufferedOutputStream bufferedOutputStream=null;
		InputStream inputStream=null;
		BufferedInputStream bufferedInputStream=null;
		try {
			url=new URL(imageUrl);
			httpURLConnection=(HttpURLConnection) url.openConnection();
			httpURLConnection.setConnectTimeout(5*1000);
			httpURLConnection.setReadTimeout(10*1000);
			httpURLConnection.setDoInput(true);
			httpURLConnection.setDoOutput(true);
			if (httpURLConnection.getResponseCode()==HttpStatus.SC_OK) {
				imageFile=new File(filePath);
				if (!imageFile.getParentFile().exists()) {
					imageFile.getParentFile().mkdirs();
				}
				if (!imageFile.exists()) {
					imageFile.createNewFile();
				}
				fileOutputStream=new FileOutputStream(imageFile);
				bufferedOutputStream=new BufferedOutputStream(fileOutputStream);
				inputStream=httpURLConnection.getInputStream();
				bufferedInputStream=new BufferedInputStream(inputStream);
				int len=0;
				byte [] buffer=new byte[1024];
				while((len=bufferedInputStream.read(buffer))!=-1){
					bufferedOutputStream.write(buffer, 0, len);
					bufferedOutputStream.flush();
				}
			} else {
				System.out.println("图片请求失败");
			}
		} catch (Exception e) {
			    System.out.println("e="+e.toString());
		}finally{
			try {
				if (fileOutputStream!=null) {
					fileOutputStream.close();
				}
				if (bufferedOutputStream!=null) {
					bufferedOutputStream.close();
				}
				if (inputStream!=null) {
					inputStream.close();
				}
				if (bufferedInputStream!=null) {
					bufferedInputStream.close();
				}
				if (httpURLConnection!=null) {
					httpURLConnection.disconnect();
				}
			} catch (Exception e) {
				 System.out.println("e="+e.toString());
			}
		}
		
	}


}



ImagesUrl如下:
package cc.patience3;

public class ImagesUrl {
	
	public static String urlStringArray []=new String []{
		"https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
		"https://img-my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
		"https://img-my.csdn.net/uploads/201308/31/1377949442_4562.jpg"
	};

}

main.xml如下:
<cc.patience3.WaterfallScrollView 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" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <LinearLayout
            android:id="@+id/firstLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />
        
         <LinearLayout
            android:id="@+id/secondLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />
         
          <LinearLayout
            android:id="@+id/thirdLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />
        
    </LinearLayout>

</cc.patience3.WaterfallScrollView>


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谷哥的小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值