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

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

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


相关文章
|
26天前
|
机器学习/深度学习 PyTorch 算法框架/工具
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
这篇文章介绍了如何使用PyTorch框架,结合CIFAR-10数据集,通过定义神经网络、损失函数和优化器,进行模型的训练和测试。
70 2
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
|
3月前
|
移动开发 TensorFlow 算法框架/工具
只保存和加载网络权重
【8月更文挑战第21天】只保存和加载网络权重。
33 2
|
9天前
|
缓存 JavaScript
Vue加载网络组件(远程组件)
【10月更文挑战第23天】在 Vue 中实现加载网络组件(远程组件)可以通过多种方式来完成。
|
3月前
|
缓存
Flutter Image从网络加载图片刷新、强制重新渲染
Flutter Image从网络加载图片刷新、强制重新渲染
109 1
|
3月前
|
SQL 网络协议 安全
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
|
3月前
|
Java Android开发 Kotlin
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
35 0
|
5月前
|
文字识别 开发工具 Android开发
视觉智能开放平台操作报错合集之使用人脸属性检测接口,出现报错:图片无法下载,请检查链接是否可访问和本地网络情况,该如何解决
在使用视觉智能开放平台时,可能会遇到各种错误和问题。虽然具体的错误代码和消息会因平台而异,但以下是一些常见错误类型及其可能的原因和解决策略的概述,包括但不限于:1. 认证错误、2. 请求参数错误、3. 资源超限、4. 图像质量问题、5. 服务不可用、6. 模型不支持的场景、7. 网络连接问题,这有助于快速定位和解决问题。
|
6月前
|
网络安全 数据安全/隐私保护 计算机视觉
2024蓝桥杯网络安全-图片隐写-缺失的数据(0基础也能学会-含代码解释)
2024蓝桥杯网络安全-图片隐写-缺失的数据(0基础也能学会-含代码解释)
|
6月前
|
XML JSON 前端开发
【Flutter前端技术开发专栏】Flutter中的图片、视频与网络资源加载
【4月更文挑战第30天】Flutter是谷歌的开源前端框架,因其高性能、流畅UI和多端运行能力受开发者喜爱。本文聚焦于Flutter中的资源加载:使用`Image`组件加载静态、网络和本地图片;通过`video_player`库加载和播放视频;利用`http`包进行网络资源请求。掌握这些技巧将有助于提升Flutter应用的开发效率和质量。
46 0
【Flutter前端技术开发专栏】Flutter中的图片、视频与网络资源加载
|
6月前
|
SQL 存储 分布式计算
Hive【基础 01】核心概念+体系架构+数据类型+内容格式+存储格式+内外部表(部分图片来源于网络)
【4月更文挑战第6天】Hive【基础 01】核心概念+体系架构+数据类型+内容格式+存储格式+内外部表(部分图片来源于网络)
125 1