Android瀑布流照片墙实现,体验不规则排列的美感

瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。

 

 

听我这么说完后,你可能会觉得瀑布流的布局非常简单嘛,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进去就好了。确实如此,如果只是为了实现功能的话,就是这么简单。可是别忘了,我们是在手机上进行开发,如果不停地往LinearLayout里添加图片,程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法,对这个算法不熟悉的朋友可以先参考Android高效加载大图、多图方案,有效避免程序OOM 。

 

下面我们就来开始实现吧,新建一个Android项目,起名叫PhotoWallFallsDemo,并选择4.0的API。

 

第一个要考虑的问题是,我们到哪儿去收集这些大小参差不齐的图片呢?这里我事先在百度上搜索了很多张风景图片,并且为了保证它们访问的稳定性,我将这些图片都上传到了我的CSDN相册里,因此只要从这里下载图片就可以了。新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:

 
  1. public class Images {

  2.  
  3. public final static String[] imageUrls = new String[] {

  4. "https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg",

  5. "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",

  6. "https://img-my.csdn.net/uploads/201309/01/1378037235_9280.jpg",

  7. "https://img-my.csdn.net/uploads/201309/01/1378037234_3539.jpg",

  8. "https://img-my.csdn.net/uploads/201309/01/1378037234_6318.jpg",

  9. "https://img-my.csdn.net/uploads/201309/01/1378037194_2965.jpg",

  10. "https://img-my.csdn.net/uploads/201309/01/1378037193_1687.jpg",

  11. "https://img-my.csdn.net/uploads/201309/01/1378037193_1286.jpg",

  12. "https://img-my.csdn.net/uploads/201309/01/1378037192_8379.jpg",

  13. "https://img-my.csdn.net/uploads/201309/01/1378037178_9374.jpg",

  14. "https://img-my.csdn.net/uploads/201309/01/1378037177_1254.jpg",

  15. "https://img-my.csdn.net/uploads/201309/01/1378037177_6203.jpg",

  16. "https://img-my.csdn.net/uploads/201309/01/1378037152_6352.jpg",

  17. "https://img-my.csdn.net/uploads/201309/01/1378037151_9565.jpg",

  18. "https://img-my.csdn.net/uploads/201309/01/1378037151_7904.jpg",

  19. "https://img-my.csdn.net/uploads/201309/01/1378037148_7104.jpg",

  20. "https://img-my.csdn.net/uploads/201309/01/1378037129_8825.jpg",

  21. "https://img-my.csdn.net/uploads/201309/01/1378037128_5291.jpg",

  22. "https://img-my.csdn.net/uploads/201309/01/1378037128_3531.jpg",

  23. "https://img-my.csdn.net/uploads/201309/01/1378037127_1085.jpg",

  24. "https://img-my.csdn.net/uploads/201309/01/1378037095_7515.jpg",

  25. "https://img-my.csdn.net/uploads/201309/01/1378037094_8001.jpg",

  26. "https://img-my.csdn.net/uploads/201309/01/1378037093_7168.jpg",

  27. "https://img-my.csdn.net/uploads/201309/01/1378037091_4950.jpg",

  28. "https://img-my.csdn.net/uploads/201308/31/1377949643_6410.jpg",

  29. "https://img-my.csdn.net/uploads/201308/31/1377949642_6939.jpg",

  30. "https://img-my.csdn.net/uploads/201308/31/1377949630_4505.jpg",

  31. "https://img-my.csdn.net/uploads/201308/31/1377949630_4593.jpg",

  32. "https://img-my.csdn.net/uploads/201308/31/1377949629_7309.jpg",

  33. "https://img-my.csdn.net/uploads/201308/31/1377949629_8247.jpg",

  34. "https://img-my.csdn.net/uploads/201308/31/1377949615_1986.jpg",

  35. "https://img-my.csdn.net/uploads/201308/31/1377949614_8482.jpg",

  36. "https://img-my.csdn.net/uploads/201308/31/1377949614_3743.jpg",

  37. "https://img-my.csdn.net/uploads/201308/31/1377949614_4199.jpg",

  38. "https://img-my.csdn.net/uploads/201308/31/1377949599_3416.jpg",

  39. "https://img-my.csdn.net/uploads/201308/31/1377949599_5269.jpg",

  40. "https://img-my.csdn.net/uploads/201308/31/1377949598_7858.jpg",

  41. "https://img-my.csdn.net/uploads/201308/31/1377949598_9982.jpg",

  42. "https://img-my.csdn.net/uploads/201308/31/1377949578_2770.jpg",

  43. "https://img-my.csdn.net/uploads/201308/31/1377949578_8744.jpg",

  44. "https://img-my.csdn.net/uploads/201308/31/1377949577_5210.jpg",

  45. "https://img-my.csdn.net/uploads/201308/31/1377949577_1998.jpg",

  46. "https://img-my.csdn.net/uploads/201308/31/1377949482_8813.jpg",

  47. "https://img-my.csdn.net/uploads/201308/31/1377949481_6577.jpg",

  48. "https://img-my.csdn.net/uploads/201308/31/1377949480_4490.jpg",

  49. "https://img-my.csdn.net/uploads/201308/31/1377949455_6792.jpg",

  50. "https://img-my.csdn.net/uploads/201308/31/1377949455_6345.jpg",

  51. "https://img-my.csdn.net/uploads/201308/31/1377949442_4553.jpg",

  52. "https://img-my.csdn.net/uploads/201308/31/1377949441_8987.jpg",

  53. "https://img-my.csdn.net/uploads/201308/31/1377949441_5454.jpg",

  54. "https://img-my.csdn.net/uploads/201308/31/1377949454_6367.jpg",

  55. "https://img-my.csdn.net/uploads/201308/31/1377949442_4562.jpg" };

  56. }

然后新建一个ImageLoader类,用于方便对图片进行管理,代码如下所示:

 
  1. public class ImageLoader {

  2.  
  3. /**

  4. * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。

  5. */

  6. private static LruCache<String, Bitmap> mMemoryCache;

  7.  
  8. /**

  9. * ImageLoader的实例。

  10. */

  11. private static ImageLoader mImageLoader;

  12.  
  13. private ImageLoader() {

  14. // 获取应用程序最大可用内存

  15. int maxMemory = (int) Runtime.getRuntime().maxMemory();

  16. int cacheSize = maxMemory / 8;

  17. // 设置图片缓存大小为程序最大可用内存的1/8

  18. mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

  19. @Override

  20. protected int sizeOf(String key, Bitmap bitmap) {

  21. return bitmap.getByteCount();

  22. }

  23. };

  24. }

  25.  
  26. /**

  27. * 获取ImageLoader的实例。

  28. *

  29. * @return ImageLoader的实例。

  30. */

  31. public static ImageLoader getInstance() {

  32. if (mImageLoader == null) {

  33. mImageLoader = new ImageLoader();

  34. }

  35. return mImageLoader;

  36. }

  37.  
  38. /**

  39. * 将一张图片存储到LruCache中。

  40. *

  41. * @param key

  42. * LruCache的键,这里传入图片的URL地址。

  43. * @param bitmap

  44. * LruCache的键,这里传入从网络上下载的Bitmap对象。

  45. */

  46. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {

  47. if (getBitmapFromMemoryCache(key) == null) {

  48. mMemoryCache.put(key, bitmap);

  49. }

  50. }

  51.  
  52. /**

  53. * 从LruCache中获取一张图片,如果不存在就返回null。

  54. *

  55. * @param key

  56. * LruCache的键,这里传入图片的URL地址。

  57. * @return 对应传入键的Bitmap对象,或者null。

  58. */

  59. public Bitmap getBitmapFromMemoryCache(String key) {

  60. return mMemoryCache.get(key);

  61. }

  62.  
  63. public static int calculateInSampleSize(BitmapFactory.Options options,

  64. int reqWidth) {

  65. // 源图片的宽度

  66. final int width = options.outWidth;

  67. int inSampleSize = 1;

  68. if (width > reqWidth) {

  69. // 计算出实际宽度和目标宽度的比率

  70. final int widthRatio = Math.round((float) width / (float) reqWidth);

  71. inSampleSize = widthRatio;

  72. }

  73. return inSampleSize;

  74. }

  75.  
  76. public static Bitmap decodeSampledBitmapFromResource(String pathName,

  77. int reqWidth) {

  78. // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小

  79. final BitmapFactory.Options options = new BitmapFactory.Options();

  80. options.inJustDecodeBounds = true;

  81. BitmapFactory.decodeFile(pathName, options);

  82. // 调用上面定义的方法计算inSampleSize值

  83. options.inSampleSize = calculateInSampleSize(options, reqWidth);

  84. // 使用获取到的inSampleSize值再次解析图片

  85. options.inJustDecodeBounds = false;

  86. return BitmapFactory.decodeFile(pathName, options);

  87. }

  88.  
  89. }

这里我们将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。

 

接下来新建MyScrollView继承自ScrollView,代码如下所示:

 
  1. public class MyScrollView extends ScrollView implements OnTouchListener {

  2.  
  3. /**

  4. * 每页要加载的图片数量

  5. */

  6. public static final int PAGE_SIZE = 15;

  7.  
  8. /**

  9. * 记录当前已加载到第几页

  10. */

  11. private int page;

  12.  
  13. /**

  14. * 每一列的宽度

  15. */

  16. private int columnWidth;

  17.  
  18. /**

  19. * 当前第一列的高度

  20. */

  21. private int firstColumnHeight;

  22.  
  23. /**

  24. * 当前第二列的高度

  25. */

  26. private int secondColumnHeight;

  27.  
  28. /**

  29. * 当前第三列的高度

  30. */

  31. private int thirdColumnHeight;

  32.  
  33. /**

  34. * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次

  35. */

  36. private boolean loadOnce;

  37.  
  38. /**

  39. * 对图片进行管理的工具类

  40. */

  41. private ImageLoader imageLoader;

  42.  
  43. /**

  44. * 第一列的布局

  45. */

  46. private LinearLayout firstColumn;

  47.  
  48. /**

  49. * 第二列的布局

  50. */

  51. private LinearLayout secondColumn;

  52.  
  53. /**

  54. * 第三列的布局

  55. */

  56. private LinearLayout thirdColumn;

  57.  
  58. /**

  59. * 记录所有正在下载或等待下载的任务。

  60. */

  61. private static Set<LoadImageTask> taskCollection;

  62.  
  63. /**

  64. * MyScrollView下的直接子布局。

  65. */

  66. private static View scrollLayout;

  67.  
  68. /**

  69. * MyScrollView布局的高度。

  70. */

  71. private static int scrollViewHeight;

  72.  
  73. /**

  74. * 记录上垂直方向的滚动距离。

  75. */

  76. private static int lastScrollY = -1;

  77.  
  78. /**

  79. * 记录所有界面上的图片,用以可以随时控制对图片的释放。

  80. */

  81. private List<ImageView> imageViewList = new ArrayList<ImageView>();

  82.  
  83. /**

  84. * 在Handler中进行图片可见性检查的判断,以及加载更多图片的操作。

  85. */

  86. private static Handler handler = new Handler() {

  87.  
  88. public void handleMessage(android.os.Message msg) {

  89. MyScrollView myScrollView = (MyScrollView) msg.obj;

  90. int scrollY = myScrollView.getScrollY();

  91. // 如果当前的滚动位置和上次相同,表示已停止滚动

  92. if (scrollY == lastScrollY) {

  93. // 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片

  94. if (scrollViewHeight + scrollY >= scrollLayout.getHeight()

  95. && taskCollection.isEmpty()) {

  96. myScrollView.loadMoreImages();

  97. }

  98. myScrollView.checkVisibility();

  99. } else {

  100. lastScrollY = scrollY;

  101. Message message = new Message();

  102. message.obj = myScrollView;

  103. // 5毫秒后再次对滚动位置进行判断

  104. handler.sendMessageDelayed(message, 5);

  105. }

  106. };

  107.  
  108. };

  109.  
  110. /**

  111. * MyScrollView的构造函数。

  112. *

  113. * @param context

  114. * @param attrs

  115. */

  116. public MyScrollView(Context context, AttributeSet attrs) {

  117. super(context, attrs);

  118. imageLoader = ImageLoader.getInstance();

  119. taskCollection = new HashSet<LoadImageTask>();

  120. setOnTouchListener(this);

  121. }

  122.  
  123. /**

  124. * 进行一些关键性的初始化操作,获取MyScrollView的高度,以及得到第一列的宽度值。并在这里开始加载第一页的图片。

  125. */

  126. @Override

  127. protected void onLayout(boolean changed, int l, int t, int r, int b) {

  128. super.onLayout(changed, l, t, r, b);

  129. if (changed && !loadOnce) {

  130. scrollViewHeight = getHeight();

  131. scrollLayout = getChildAt(0);

  132. firstColumn = (LinearLayout) findViewById(R.id.first_column);

  133. secondColumn = (LinearLayout) findViewById(R.id.second_column);

  134. thirdColumn = (LinearLayout) findViewById(R.id.third_column);

  135. columnWidth = firstColumn.getWidth();

  136. loadOnce = true;

  137. loadMoreImages();

  138. }

  139. }

  140.  
  141. /**

  142. * 监听用户的触屏事件,如果用户手指离开屏幕则开始进行滚动检测。

  143. */

  144. @Override

  145. public boolean onTouch(View v, MotionEvent event) {

  146. if (event.getAction() == MotionEvent.ACTION_UP) {

  147. Message message = new Message();

  148. message.obj = this;

  149. handler.sendMessageDelayed(message, 5);

  150. }

  151. return false;

  152. }

  153.  
  154. /**

  155. * 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。

  156. */

  157. public void loadMoreImages() {

  158. if (hasSDCard()) {

  159. int startIndex = page * PAGE_SIZE;

  160. int endIndex = page * PAGE_SIZE + PAGE_SIZE;

  161. if (startIndex < Images.imageUrls.length) {

  162. Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT)

  163. .show();

  164. if (endIndex > Images.imageUrls.length) {

  165. endIndex = Images.imageUrls.length;

  166. }

  167. for (int i = startIndex; i < endIndex; i++) {

  168. LoadImageTask task = new LoadImageTask();

  169. taskCollection.add(task);

  170. task.execute(Images.imageUrls[i]);

  171. }

  172. page++;

  173. } else {

  174. Toast.makeText(getContext(), "已没有更多图片", Toast.LENGTH_SHORT)

  175. .show();

  176. }

  177. } else {

  178. Toast.makeText(getContext(), "未发现SD卡", Toast.LENGTH_SHORT).show();

  179. }

  180. }

  181.  
  182. /**

  183. * 遍历imageViewList中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。

  184. */

  185. public void checkVisibility() {

  186. for (int i = 0; i < imageViewList.size(); i++) {

  187. ImageView imageView = imageViewList.get(i);

  188. int borderTop = (Integer) imageView.getTag(R.string.border_top);

  189. int borderBottom = (Integer) imageView

  190. .getTag(R.string.border_bottom);

  191. if (borderBottom > getScrollY()

  192. && borderTop < getScrollY() + scrollViewHeight) {

  193. String imageUrl = (String) imageView.getTag(R.string.image_url);

  194. Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl);

  195. if (bitmap != null) {

  196. imageView.setImageBitmap(bitmap);

  197. } else {

  198. LoadImageTask task = new LoadImageTask(imageView);

  199. task.execute(imageUrl);

  200. }

  201. } else {

  202. imageView.setImageResource(R.drawable.empty_photo);

  203. }

  204. }

  205. }

  206.  
  207. /**

  208. * 判断手机是否有SD卡。

  209. *

  210. * @return 有SD卡返回true,没有返回false。

  211. */

  212. private boolean hasSDCard() {

  213. return Environment.MEDIA_MOUNTED.equals(Environment

  214. .getExternalStorageState());

  215. }

  216.  
  217. /**

  218. * 异步下载图片的任务。

  219. *

  220. * @author guolin

  221. */

  222. class LoadImageTask extends AsyncTask<String, Void, Bitmap> {

  223.  
  224. /**

  225. * 图片的URL地址

  226. */

  227. private String mImageUrl;

  228.  
  229. /**

  230. * 可重复使用的ImageView

  231. */

  232. private ImageView mImageView;

  233.  
  234. public LoadImageTask() {

  235. }

  236.  
  237. /**

  238. * 将可重复使用的ImageView传入

  239. *

  240. * @param imageView

  241. */

  242. public LoadImageTask(ImageView imageView) {

  243. mImageView = imageView;

  244. }

  245.  
  246. @Override

  247. protected Bitmap doInBackground(String... params) {

  248. mImageUrl = params[0];

  249. Bitmap imageBitmap = imageLoader

  250. .getBitmapFromMemoryCache(mImageUrl);

  251. if (imageBitmap == null) {

  252. imageBitmap = loadImage(mImageUrl);

  253. }

  254. return imageBitmap;

  255. }

  256.  
  257. @Override

  258. protected void onPostExecute(Bitmap bitmap) {

  259. if (bitmap != null) {

  260. double ratio = bitmap.getWidth() / (columnWidth * 1.0);

  261. int scaledHeight = (int) (bitmap.getHeight() / ratio);

  262. addImage(bitmap, columnWidth, scaledHeight);

  263. }

  264. taskCollection.remove(this);

  265. }

  266.  
  267. /**

  268. * 根据传入的URL,对图片进行加载。如果这张图片已经存在于SD卡中,则直接从SD卡里读取,否则就从网络上下载。

  269. *

  270. * @param imageUrl

  271. * 图片的URL地址

  272. * @return 加载到内存的图片。

  273. */

  274. private Bitmap loadImage(String imageUrl) {

  275. File imageFile = new File(getImagePath(imageUrl));

  276. if (!imageFile.exists()) {

  277. downloadImage(imageUrl);

  278. }

  279. if (imageUrl != null) {

  280. Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(

  281. imageFile.getPath(), columnWidth);

  282. if (bitmap != null) {

  283. imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);

  284. return bitmap;

  285. }

  286. }

  287. return null;

  288. }

  289.  
  290. /**

  291. * 向ImageView中添加一张图片

  292. *

  293. * @param bitmap

  294. * 待添加的图片

  295. * @param imageWidth

  296. * 图片的宽度

  297. * @param imageHeight

  298. * 图片的高度

  299. */

  300. private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) {

  301. LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(

  302. imageWidth, imageHeight);

  303. if (mImageView != null) {

  304. mImageView.setImageBitmap(bitmap);

  305. } else {

  306. ImageView imageView = new ImageView(getContext());

  307. imageView.setLayoutParams(params);

  308. imageView.setImageBitmap(bitmap);

  309. imageView.setScaleType(ScaleType.FIT_XY);

  310. imageView.setPadding(5, 5, 5, 5);

  311. imageView.setTag(R.string.image_url, mImageUrl);

  312. findColumnToAdd(imageView, imageHeight).addView(imageView);

  313. imageViewList.add(imageView);

  314. }

  315. }

  316.  
  317. /**

  318. * 找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。

  319. *

  320. * @param imageView

  321. * @param imageHeight

  322. * @return 应该添加图片的一列

  323. */

  324. private LinearLayout findColumnToAdd(ImageView imageView,

  325. int imageHeight) {

  326. if (firstColumnHeight <= secondColumnHeight) {

  327. if (firstColumnHeight <= thirdColumnHeight) {

  328. imageView.setTag(R.string.border_top, firstColumnHeight);

  329. firstColumnHeight += imageHeight;

  330. imageView.setTag(R.string.border_bottom, firstColumnHeight);

  331. return firstColumn;

  332. }

  333. imageView.setTag(R.string.border_top, thirdColumnHeight);

  334. thirdColumnHeight += imageHeight;

  335. imageView.setTag(R.string.border_bottom, thirdColumnHeight);

  336. return thirdColumn;

  337. } else {

  338. if (secondColumnHeight <= thirdColumnHeight) {

  339. imageView.setTag(R.string.border_top, secondColumnHeight);

  340. secondColumnHeight += imageHeight;

  341. imageView

  342. .setTag(R.string.border_bottom, secondColumnHeight);

  343. return secondColumn;

  344. }

  345. imageView.setTag(R.string.border_top, thirdColumnHeight);

  346. thirdColumnHeight += imageHeight;

  347. imageView.setTag(R.string.border_bottom, thirdColumnHeight);

  348. return thirdColumn;

  349. }

  350. }

  351.  
  352. /**

  353. * 将图片下载到SD卡缓存起来。

  354. *

  355. * @param imageUrl

  356. * 图片的URL地址。

  357. */

  358. private void downloadImage(String imageUrl) {

  359. HttpURLConnection con = null;

  360. FileOutputStream fos = null;

  361. BufferedOutputStream bos = null;

  362. BufferedInputStream bis = null;

  363. File imageFile = null;

  364. try {

  365. URL url = new URL(imageUrl);

  366. con = (HttpURLConnection) url.openConnection();

  367. con.setConnectTimeout(5 * 1000);

  368. con.setReadTimeout(15 * 1000);

  369. con.setDoInput(true);

  370. con.setDoOutput(true);

  371. bis = new BufferedInputStream(con.getInputStream());

  372. imageFile = new File(getImagePath(imageUrl));

  373. fos = new FileOutputStream(imageFile);

  374. bos = new BufferedOutputStream(fos);

  375. byte[] b = new byte[1024];

  376. int length;

  377. while ((length = bis.read(b)) != -1) {

  378. bos.write(b, 0, length);

  379. bos.flush();

  380. }

  381. } catch (Exception e) {

  382. e.printStackTrace();

  383. } finally {

  384. try {

  385. if (bis != null) {

  386. bis.close();

  387. }

  388. if (bos != null) {

  389. bos.close();

  390. }

  391. if (con != null) {

  392. con.disconnect();

  393. }

  394. } catch (IOException e) {

  395. e.printStackTrace();

  396. }

  397. }

  398. if (imageFile != null) {

  399. Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(

  400. imageFile.getPath(), columnWidth);

  401. if (bitmap != null) {

  402. imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);

  403. }

  404. }

  405. }

  406.  
  407. /**

  408. * 获取图片的本地存储路径。

  409. *

  410. * @param imageUrl

  411. * 图片的URL地址。

  412. * @return 图片的本地存储路径。

  413. */

  414. private String getImagePath(String imageUrl) {

  415. int lastSlashIndex = imageUrl.lastIndexOf("/");

  416. String imageName = imageUrl.substring(lastSlashIndex + 1);

  417. String imageDir = Environment.getExternalStorageDirectory()

  418. .getPath() + "/PhotoWallFalls/";

  419. File file = new File(imageDir);

  420. if (!file.exists()) {

  421. file.mkdirs();

  422. }

  423. String imagePath = imageDir + imageName;

  424. return imagePath;

  425. }

  426. }

  427.  
  428. }

MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。

 

那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。

 

另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。

 

然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:

 
  1. <com.example.photowallfallsdemo.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"

  2. android:id="@+id/my_scroll_view"

  3. android:layout_width="match_parent"

  4. android:layout_height="match_parent" >

  5.  
  6. <LinearLayout

  7. android:layout_width="match_parent"

  8. android:layout_height="wrap_content"

  9. android:orientation="horizontal" >

  10.  
  11. <LinearLayout

  12. android:id="@+id/first_column"

  13. android:layout_width="0dp"

  14. android:layout_height="wrap_content"

  15. android:layout_weight="1"

  16. android:orientation="vertical" >

  17. </LinearLayout>

  18.  
  19. <LinearLayout

  20. android:id="@+id/second_column"

  21. android:layout_width="0dp"

  22. android:layout_height="wrap_content"

  23. android:layout_weight="1"

  24. android:orientation="vertical" >

  25. </LinearLayout>

  26.  
  27. <LinearLayout

  28. android:id="@+id/third_column"

  29. android:layout_width="0dp"

  30. android:layout_height="wrap_content"

  31. android:layout_weight="1"

  32. android:orientation="vertical" >

  33. </LinearLayout>

  34. </LinearLayout>

  35.  
  36. </com.example.photowallfallsdemo.MyScrollView>

可以看到,这里我们使用了刚才编写好的MyScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,这样在MyScrollView中就可以动态地向这三个LinearLayout里添加图片了。

 

最后,由于我们使用到了网络和SD卡存储的功能,因此还需要在AndroidManifest.xml中添加以下权限:

 
  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  2. <uses-permission android:name="android.permission.INTERNET" />

这样我们所有的编码工作就已经完成了,现在可以尝试运行一下,效果如下图所示:

 

 

瀑布流模式的照片墙果真非常美观吧,而且由于我们有非常完善的资源释放机制,不管你在照片墙上添加了多少图片,程序占用内存始终都会保持在一个合理的范围内。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值