iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android App中实现相册瀑布流展示的实例分享
  • 721
分享到

Android App中实现相册瀑布流展示的实例分享

展示瀑布瀑布流appAndroid 2022-06-06 08:06:52 721人浏览 泡泡鱼
摘要

传统界面的布局方式总是行列分明、坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳。这个时候瀑布流布局的出现,就给人带来了耳目一新的感觉,这种布局虽然看

传统界面的布局方式总是行列分明、坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳。这个时候瀑布流布局的出现,就给人带来了耳目一新的感觉,这种布局虽然看上去貌似毫无规律,但是却有一种说不上来的美感,以至于涌现出了大批的网站和应用纷纷使用这种新颖的布局来设计界面。
记得我在之前已经写过一篇关于如何在Android上实现照片墙功能的文章了,但那个时候是使用的GridView来进行布局的,这种布局方式只适用于“墙”上的每张图片大小都相同的情况,如果图片的大小参差不齐,在GridView中显示就会非常的难看。而使用瀑布流的布局方式就可以很好地解决这个问题,因此今天我们也来赶一下潮流,看看如何在Android上实现瀑布流照片墙的功能。
首先还是讲一下实现原理,瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。

201648235457901.png (347×459)

听我这么说完后,你可能会觉得瀑布流的布局非常简单嘛,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进去就好了。确实如此,如果只是为了实现功能的话,就是这么简单。可是别忘了,我们是在手机上进行开发,如果不停地往LinearLayout里添加图片,程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法,这个具体的在文后会专门讲,先知道是用这么回事~
下面我们就来开始实现吧,新建一个Android项目,起名叫PhotoWallFallsDemo,并选择4.0的api
第一个要考虑的问题是,我们到哪儿去收集这些大小参差不齐的图片呢?这里我事先在百度上搜索了很多张风景图片,并且为了保证它们访问的稳定性,我将这些图片都上传到了我的CSDN相册里,因此只要从这里下载图片就可以了。新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:


public class Images { 
 public final static String[] imageUrls = 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" }; 
} 

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


public class ImageLoader { 
  
 private static LruCache<String, Bitmap> mMemoryCache; 
  
 private static ImageLoader mImageLoader; 
 private ImageLoader() { 
  // 获取应用程序最大可用内存 
  int maxMemory = (int) Runtime.getRuntime().maxMemory(); 
  int cacheSize = maxMemory / 8; 
  // 设置图片缓存大小为程序最大可用内存的1/8 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
   @Override 
   protected int sizeOf(String key, Bitmap bitmap) { 
    return bitmap.getByteCount(); 
   } 
  }; 
 } 
  
 public static ImageLoader getInstance() { 
  if (mImageLoader == null) { 
   mImageLoader = new ImageLoader(); 
  } 
  return mImageLoader; 
 } 
  
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
  if (getBitmapFromMemoryCache(key) == null) { 
   mMemoryCache.put(key, bitmap); 
  } 
 } 
  
 public Bitmap getBitmapFromMemoryCache(String key) { 
  return mMemoryCache.get(key); 
 } 
 public static int calculateInSampleSize(BitmapFactory.Options options, 
   int reqWidth) { 
  // 源图片的宽度 
  final int width = options.outWidth; 
  int inSampleSize = 1; 
  if (width > reqWidth) { 
   // 计算出实际宽度和目标宽度的比率 
   final int widthRatio = Math.round((float) width / (float) reqWidth); 
   inSampleSize = widthRatio; 
  } 
  return inSampleSize; 
 } 
 public static Bitmap decodeSampledBitmapFromResource(String pathName, 
   int reqWidth) { 
  // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 
  final BitmapFactory.Options options = new BitmapFactory.Options(); 
  options.inJustDecodeBounds = true; 
  BitmapFactory.decodeFile(pathName, options); 
  // 调用上面定义的方法计算inSampleSize值 
  options.inSampleSize = calculateInSampleSize(options, reqWidth); 
  // 使用获取到的inSampleSize值再次解析图片 
  options.inJustDecodeBounds = false; 
  return BitmapFactory.decodeFile(pathName, options); 
 } 
} 

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


public class MyScrollView extends ScrollView implements OnTouchListener { 
  
 public static final int PAGE_SIZE = 15; 
  
 private int page; 
  
 private int columnWidth; 
  
 private int firstColumnHeight; 
  
 private int secondColumnHeight; 
  
 private int thirdColumnHeight; 
  
 private boolean loadOnce; 
  
 private ImageLoader imageLoader; 
  
 private LinearLayout firstColumn; 
  
 private LinearLayout secondColumn; 
  
 private LinearLayout thirdColumn; 
  
 private static Set<LoadImageTask> taskCollection; 
  
 private static View scrollLayout; 
  
 private static int scrollViewHeight; 
  
 private static int lastScrollY = -1; 
  
 private List<ImageView> imageViewList = new ArrayList<ImageView>(); 
  
 private static Handler handler = new Handler() { 
  public void handleMessage(android.os.Message msg) { 
   MyScrollView myScrollView = (MyScrollView) msg.obj; 
   int scrollY = myScrollView.getScrollY(); 
   // 如果当前的滚动位置和上次相同,表示已停止滚动 
   if (scrollY == lastScrollY) { 
    // 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片 
    if (scrollViewHeight + scrollY >= scrollLayout.getHeight() 
      && taskCollection.isEmpty()) { 
     myScrollView.loadMoreImages(); 
    } 
    myScrollView.checkVisibility(); 
   } else { 
    lastScrollY = scrollY; 
    Message message = new Message(); 
    message.obj = myScrollView; 
    // 5毫秒后再次对滚动位置进行判断 
    handler.sendMessageDelayed(message, 5); 
   } 
  }; 
 }; 
  
 public MyScrollView(Context context, AttributeSet attrs) { 
  super(context, attrs); 
  imageLoader = ImageLoader.getInstance(); 
  taskCollection = new HashSet<LoadImageTask>(); 
  setOnTouchListener(this); 
 } 
  
 @Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) { 
  super.onLayout(changed, l, t, r, b); 
  if (changed && !loadOnce) { 
   scrollViewHeight = getHeight(); 
   scrollLayout = getChildAt(0); 
   firstColumn = (LinearLayout) findViewById(R.id.first_column); 
   secondColumn = (LinearLayout) findViewById(R.id.second_column); 
   thirdColumn = (LinearLayout) findViewById(R.id.third_column); 
   columnWidth = firstColumn.getWidth(); 
   loadOnce = true; 
   loadMoreImages(); 
  } 
 } 
  
 @Override 
 public boolean onTouch(View v, MotionEvent event) { 
  if (event.getAction() == MotionEvent.ACTION_UP) { 
   Message message = new Message(); 
   message.obj = this; 
   handler.sendMessageDelayed(message, 5); 
  } 
  return false; 
 } 
  
 public void loadMoreImages() { 
  if (hasSDCard()) { 
   int startIndex = page * PAGE_SIZE; 
   int endIndex = page * PAGE_SIZE + PAGE_SIZE; 
   if (startIndex < Images.imageUrls.length) { 
    Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT) 
      .show(); 
    if (endIndex > Images.imageUrls.length) { 
     endIndex = Images.imageUrls.length; 
    } 
    for (int i = startIndex; i < endIndex; i++) { 
     LoadImageTask task = new LoadImageTask(); 
     taskCollection.add(task); 
     task.execute(Images.imageUrls[i]); 
    } 
    page++; 
   } else { 
    Toast.makeText(getContext(), "已没有更多图片", Toast.LENGTH_SHORT) 
      .show(); 
   } 
  } else { 
   Toast.makeText(getContext(), "未发现SD卡", Toast.LENGTH_SHORT).show(); 
  } 
 } 
  
 public void checkVisibility() { 
  for (int i = 0; i < imageViewList.size(); i++) { 
   ImageView imageView = imageViewList.get(i); 
   int borderTop = (Integer) imageView.getTag(R.string.border_top); 
   int borderBottom = (Integer) imageView 
     .getTag(R.string.border_bottom); 
   if (borderBottom > getScrollY() 
     && borderTop < getScrollY() + scrollViewHeight) { 
    String imageUrl = (String) imageView.getTag(R.string.image_url); 
    Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl); 
    if (bitmap != null) { 
     imageView.setImageBitmap(bitmap); 
    } else { 
     LoadImageTask task = new LoadImageTask(imageView); 
     task.execute(imageUrl); 
    } 
   } else { 
    imageView.setImageResource(R.drawable.empty_photo); 
   } 
  } 
 } 
  
 private boolean hasSDCard() { 
  return Environment.MEDIA_MOUNTED.equals(Environment 
    .getExternalStorageState()); 
 } 
  
 class LoadImageTask extends AsyncTask<String, Void, Bitmap> { 
   
  private String mImageUrl; 
   
  private ImageView mImageView; 
  public LoadImageTask() { 
  } 
   
  public LoadImageTask(ImageView imageView) { 
   mImageView = imageView; 
  } 
  @Override 
  protected Bitmap doInBackground(String... params) { 
   mImageUrl = params[0]; 
   Bitmap imageBitmap = imageLoader 
     .getBitmapFromMemoryCache(mImageUrl); 
   if (imageBitmap == null) { 
    imageBitmap = loadImage(mImageUrl); 
   } 
   return imageBitmap; 
  } 
  @Override 
  protected void onPostExecute(Bitmap bitmap) { 
   if (bitmap != null) { 
    double ratio = bitmap.getWidth() / (columnWidth * 1.0); 
    int scaledHeight = (int) (bitmap.getHeight() / ratio); 
    addImage(bitmap, columnWidth, scaledHeight); 
   } 
   taskCollection.remove(this); 
  } 
   
  private Bitmap loadImage(String imageUrl) { 
   File imageFile = new File(getImagePath(imageUrl)); 
   if (!imageFile.exists()) { 
    downloadImage(imageUrl); 
   } 
   if (imageUrl != null) { 
    Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource( 
      imageFile.getPath(), columnWidth); 
    if (bitmap != null) { 
     imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); 
     return bitmap; 
    } 
   } 
   return null; 
  } 
   
  private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) { 
   LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 
     imageWidth, imageHeight); 
   if (mImageView != null) { 
    mImageView.setImageBitmap(bitmap); 
   } else { 
    ImageView imageView = new ImageView(getContext()); 
    imageView.setLayoutParams(params); 
    imageView.setImageBitmap(bitmap); 
    imageView.setScaleType(ScaleType.FIT_XY); 
    imageView.setPadding(5, 5, 5, 5); 
    imageView.setTag(R.string.image_url, mImageUrl); 
    findColumnToAdd(imageView, imageHeight).addView(imageView); 
    imageViewList.add(imageView); 
   } 
  } 
   
  private LinearLayout findColumnToAdd(ImageView imageView, 
    int imageHeight) { 
   if (firstColumnHeight <= secondColumnHeight) { 
    if (firstColumnHeight <= thirdColumnHeight) { 
     imageView.setTag(R.string.border_top, firstColumnHeight); 
     firstColumnHeight += imageHeight; 
     imageView.setTag(R.string.border_bottom, firstColumnHeight); 
     return firstColumn; 
    } 
    imageView.setTag(R.string.border_top, thirdColumnHeight); 
    thirdColumnHeight += imageHeight; 
    imageView.setTag(R.string.border_bottom, thirdColumnHeight); 
    return thirdColumn; 
   } else { 
    if (secondColumnHeight <= thirdColumnHeight) { 
     imageView.setTag(R.string.border_top, secondColumnHeight); 
     secondColumnHeight += imageHeight; 
     imageView 
       .setTag(R.string.border_bottom, secondColumnHeight); 
     return secondColumn; 
    } 
    imageView.setTag(R.string.border_top, thirdColumnHeight); 
    thirdColumnHeight += imageHeight; 
    imageView.setTag(R.string.border_bottom, thirdColumnHeight); 
    return thirdColumn; 
   } 
  } 
   
  private void downloadImage(String imageUrl) { 
   HttpURLConnection con = null; 
   FileOutputStream fos = null; 
   BufferedOutputStream bos = null; 
   BufferedInputStream bis = null; 
   File imageFile = null; 
   try { 
    URL url = new URL(imageUrl); 
    con = (HttpURLConnection) url.openConnection(); 
    con.setConnectTimeout(5 * 1000); 
    con.setReadTimeout(15 * 1000); 
    con.setDoInput(true); 
    con.setDoOutput(true); 
    bis = new BufferedInputStream(con.getInputStream()); 
    imageFile = new File(getImagePath(imageUrl)); 
    fos = new FileOutputStream(imageFile); 
    bos = new BufferedOutputStream(fos); 
    byte[] b = new byte[1024]; 
    int length; 
    while ((length = bis.read(b)) != -1) { 
     bos.write(b, 0, length); 
     bos.flush(); 
    } 
   } catch (Exception e) { 
    e.printStackTrace(); 
   } finally { 
    try { 
     if (bis != null) { 
      bis.close(); 
     } 
     if (bos != null) { 
      bos.close(); 
     } 
     if (con != null) { 
      con.disconnect(); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
   } 
   if (imageFile != null) { 
    Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource( 
      imageFile.getPath(), columnWidth); 
    if (bitmap != null) { 
     imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); 
    } 
   } 
  } 
   
  private String getImagePath(String imageUrl) { 
   int lastSlashIndex = imageUrl.lastIndexOf("/"); 
   String imageName = imageUrl.substring(lastSlashIndex + 1); 
   String imageDir = Environment.getExternalStorageDirectory() 
     .getPath() + "/PhotoWallFalls/"; 
   File file = new File(imageDir); 
   if (!file.exists()) { 
    file.mkdirs(); 
   } 
   String imagePath = imageDir + imageName; 
   return imagePath; 
  } 
 } 
} 

MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。
那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。
然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:


<com.example.photowallfallsdemo.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/my_scroll_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 <LinearLayout 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:orientation="horizontal" > 
  <LinearLayout 
   android:id="@+id/first_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
  <LinearLayout 
   android:id="@+id/second_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
  <LinearLayout 
   android:id="@+id/third_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
 </LinearLayout> 
</com.example.photowallfallsdemo.MyScrollView>

 
可以看到,这里我们使用了刚才编写好的MyScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,这样在MyScrollView中就可以动态地向这三个LinearLayout里添加图片了。
最后,由于我们使用到了网络和SD卡存储的功能,因此还需要在AndroidManifest.xml中添加以下权限:


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

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

201648235542999.gif (235×418)

LruCache图片缓存技术
在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。
这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
你的设备可以为每个应用程序分配多大的内存?
设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
图片的尺寸和大小,还有每张图片会占据多少内存空间。
图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。
下面是一个使用 LruCache 来缓存图片的例子:


private LruCache<String, Bitmap> mMemoryCache; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
  // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 
  // LruCache通过构造函数传入缓存值,以KB为单位。 
  int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
  // 使用最大可用内存值的1/8作为缓存的大小。 
  int cacheSize = maxMemory / 8; 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
    @Override 
    protected int sizeOf(String key, Bitmap bitmap) { 
      // 重写此方法来衡量每张图片的大小,默认返回图片数量。 
      return bitmap.getByteCount() / 1024; 
    } 
  }; 
} 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
  if (getBitmapFromMemCache(key) == null) { 
    mMemoryCache.put(key, bitmap); 
  } 
} 
public Bitmap getBitmapFromMemCache(String key) { 
  return mMemoryCache.get(key); 
} 

在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。


public void loadBitmap(int resId, ImageView imageView) { 
  final String imageKey = String.valueOf(resId); 
  final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
  if (bitmap != null) { 
    imageView.setImageBitmap(bitmap); 
  } else { 
    imageView.setImageResource(R.drawable.image_placeholder); 
    BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
    task.execute(resId); 
  } 
} 

BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
  // 在后台加载图片。 
  @Override 
  protected Bitmap doInBackground(Integer... params) { 
    final Bitmap bitmap = decodeSampledBitmapFromResource( 
        getResources(), params[0], 100, 100); 
    addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
    return bitmap; 
  } 
} 

掌握了以上两种方法,不管是要在程序中加载超大图片,还是要加载大量图片,都不用担心OOM的问题了!

您可能感兴趣的文章:Android瀑布流照片墙实现 体验不规则排列的美感Android RecyclerView详解之实现 ListView GridView瀑布流效果android中UIColletionView瀑布流布局实现思路以及封装的实现android控件封装 自己封装的dialog控件android 自定义控件 自定义属性详细介绍Android中Spinner(下拉框)控件的使用详解Android下拉刷新上拉加载控件(适用于所有View)Android控件系列之TextView使用介绍android ListView和ProgressBar(进度条控件)的使用方法Android控件之ListView用法实例详解Android开发之瀑布流控件的实现与使用方法示例


--结束END--

本文标题: Android App中实现相册瀑布流展示的实例分享

本文链接: https://www.lsjlt.com/news/25241.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Django瀑布流的实现示例
    目录需求分析实现流程模型设计视图函数模板settings 配置urlconf 配置包管理模型使用对象封装全局变量需求分析 现在是 "图片为王"的时代,在浏览一些网...
    99+
    2023-03-23
    Django瀑布流
  • JavaScript实现瀑布流布局的代码分享
    目录前言如何实现html部分css部分js部分瀑布流布局的优点前言 不知道大家在线上购物的时候有没有发现到,自己逛起来根本就停不下来,越往下翻越是觉得就会出现需要的东西。这就是很多电...
    99+
    2023-05-15
    JavaScript实现瀑布流布局 JavaScript瀑布流布局 JavaScript瀑布流
  • 如何在uni-app项目中实现瀑布流布局
    本篇文章为大家展示了如何在uni-app项目中实现瀑布流布局,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。插件代码拷贝下载后把components目录下waterfall.vue文件拷贝到自己项目目...
    99+
    2023-06-08
  • Android中FlowLayout组件实现瀑布流效果
    目录FlowLayout实现关键步骤:1、创建一个view继承自ViewGroup2、重写并实现onMeasure方法3、重写并实现onLayout方法总结纸上得来终觉浅,绝知此事要...
    99+
    2024-04-02
  • CSS3如何实现瀑布流布局与无限加载图片相册
    这篇文章主要介绍了CSS3如何实现瀑布流布局与无限加载图片相册,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、pic1.html页面代码如...
    99+
    2024-04-02
  • 如何使用HTML和CSS实现瀑布流图片展示布局
    瀑布流布局是一种常用于图片展示的布局方式,具有美观和灵活的特点。它能够根据图片的尺寸自动排列,使整个页面看起来更加有趣和吸引人。本文将介绍如何使用HTML和CSS来实现瀑布流图片展示布局,并提供具体的代码示例。第一步:创建HTML结构首先,...
    99+
    2023-10-21
    CSS html 瀑布流布局
  • Android中怎么实现一个瀑布流控件
    本篇文章给大家分享的是有关Android中怎么实现一个瀑布流控件,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。具体如下:public class FlowL...
    99+
    2023-05-30
    android
  • 如何使用HTML和CSS实现瀑布流商品展示布局
    瀑布流布局是一种常见的网页设计方式,特点是呈现出错落有致、动态有序的视觉效果。在商品展示网页中应用瀑布流布局可以提高商品的展示效果,吸引用户的注意力。本文将介绍如何使用 HTML 和 CSS 实现瀑布流商品展示布局,并提供具体的代码示例。一...
    99+
    2023-10-21
    CSS html 瀑布流布局
  • Android中FlowLayout组件如何实现瀑布流效果
    这篇文章将为大家详细讲解有关Android中FlowLayout组件如何实现瀑布流效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。纸上得来终觉浅,绝知此事要躬行。动手实践是学习的最好的方式,对于自定义V...
    99+
    2023-06-26
  • vue+uniapp瀑布流布局多种实现方式示例代码
    目录前言一、实现原理二、代码实现三.uniapp实现四、多列实现总结前言 瀑布流布局是网页设计常见的一种布局,一般用于图片多列展示。列宽固定,图片根据自身高度自适应交错排列。 一、...
    99+
    2023-03-23
    uniapp实现瀑布流 uni-app瀑布流 vue实现瀑布流布局
  • Android冷启动实现app秒开的示例分析
    这篇文章将为大家详细讲解有关Android冷启动实现app秒开的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。具体如下:AndroidManifest里对应activity添加属性android:...
    99+
    2023-05-30
    android app
  • 微信小程序实现可以截断的瀑布流组件的示例代码
    瀑布流是一种常见的布局方式,实现的方式有许多,比如直接分两列,然后控制在左右两列加入元素;还有一种方式就是通过绝对定位的方式来放置两边。本文所要介绍的瀑布流不同于常规的,因为瀑布流中...
    99+
    2024-04-02
  • Golang分布式注册中心实现流程讲解
    目录动手实现一个分布式注册中心日志服务log/Server.golog/Client.go主启动程序LogService服务启动与注册service/service.go服务注册与发...
    99+
    2023-05-19
    Golang分布式注册中心 Golang注册中心
  • Android仿微信布局的实现示例
    目前没有实现微信的功能,只是对微信的各个界面的调动以及对通讯录,发现和我中各个按钮的设置,同时如果你想尝试给微信中各个按钮背后添加功能时间可以用此作为模板哦,如拍照,朋友圈的添加都可...
    99+
    2024-04-02
  • Android 实现IOS选择拍照相册底部弹出的实例
    Android 实现IOS选择拍照相册底部弹出的实例效果图1. AndroidStudio使用dependencies { compile 'com.guoqi.widget:actionsheet:1.0'}...
    99+
    2023-05-30
    android ios 拍照
  • redis分布式锁的实现示例
    小编给大家分享一下redis分布式锁的实现示例,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Redissonredisson和下...
    99+
    2024-04-02
  • Android App实现闪屏页广告图的全屏显示实例
    目录1. 适配长屏幕的全面屏2. 适配刘海屏或者水滴屏凹形屏幕的显示模式1. 适配长屏幕的全面屏 至于全屏展示,就得做适配工作,有以下两种方式可进行适配: 在 Android 8.0...
    99+
    2024-04-02
  • springboot+redis 实现分布式限流令牌桶的示例代码
    1、前言 网上找了很多redis分布式限流方案,要不就是太大,需要引入第三方jar,而且还无法正常运行,要不就是定时任务定时往key中放入数据,使用的时候调用,严重影响性能,所以着手...
    99+
    2024-04-02
  • Android实现动态改变app图标的示例代码
    本文介绍了动态改变app图标,分享给大家,具体如下:代码实现如下:<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:l...
    99+
    2023-05-30
    android app图标 pp
  • Android实现新浪微博一键分享的实例代码
    写在本章前愈来愈多的APP支持一键分享至QQ空间、微信朋友圈、新浪微博的功能,同时支持第三方账号登录,如QQ、微信、新浪微博等第三方平台的账号。本章结合当下流行的设计,兼顾免费的开源ShareSDK,结合项目中的实际需求,整合出一套分享源码...
    99+
    2023-05-30
    android 微博分享 roi
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作