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); } } } }
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); } } }
package cc.patience3; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; 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卡中获取图片 */ public static Bitmap getBitmapFromSDCard(String filePath,int requestWidth){ Bitmap bitmap=null; Options options=new Options(); BitmapFactory.decodeFile(filePath, options); options.inJustDecodeBounds=true; options.inSampleSize=calculateInSampleSize(options,requestWidth); options.inJustDecodeBounds=false; bitmap=BitmapFactory.decodeFile(filePath, options); 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()); } } } }
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" }; }
<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>
利用LruCache加载网络图片实现图片瀑布流效果(基础版),布布扣,bubuko.com
利用LruCache加载网络图片实现图片瀑布流效果(基础版)
原文:http://blog.csdn.net/lfdfhl/article/details/38535903