亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android App中實(shí)現(xiàn)相冊(cè)瀑布流展示的實(shí)例分享

 更新時(shí)間:2016年04月08日 23:57:26   作者:guolin  
這篇文章主要介紹了Android App中實(shí)現(xiàn)相冊(cè)瀑布流展示的實(shí)例分享,例子中利用到了緩存LruCache類(lèi)的相關(guān)算法來(lái)解決大量加載問(wèn)題,需要的朋友可以參考下

傳統(tǒng)界面的布局方式總是行列分明、坐落有序的,這種布局已是司空見(jiàn)慣,在不知不覺(jué)中大家都已經(jīng)對(duì)它產(chǎn)生了審美疲勞。這個(gè)時(shí)候瀑布流布局的出現(xiàn),就給人帶來(lái)了耳目一新的感覺(jué),這種布局雖然看上去貌似毫無(wú)規(guī)律,但是卻有一種說(shuō)不上來(lái)的美感,以至于涌現(xiàn)出了大批的網(wǎng)站和應(yīng)用紛紛使用這種新穎的布局來(lái)設(shè)計(jì)界面。
記得我在之前已經(jīng)寫(xiě)過(guò)一篇關(guān)于如何在Android上實(shí)現(xiàn)照片墻功能的文章了,但那個(gè)時(shí)候是使用的GridView來(lái)進(jìn)行布局的,這種布局方式只適用于“墻”上的每張圖片大小都相同的情況,如果圖片的大小參差不齊,在GridView中顯示就會(huì)非常的難看。而使用瀑布流的布局方式就可以很好地解決這個(gè)問(wèn)題,因此今天我們也來(lái)趕一下潮流,看看如何在Android上實(shí)現(xiàn)瀑布流照片墻的功能。
首先還是講一下實(shí)現(xiàn)原理,瀑布流的布局方式雖然看起來(lái)好像排列的很隨意,其實(shí)它是有很科學(xué)的排列規(guī)則的。整個(gè)界面會(huì)根據(jù)屏幕的寬度劃分成等寬的若干列,由于手機(jī)的屏幕不是很大,這里我們就分成三列。每當(dāng)需要添加一張圖片時(shí),會(huì)將這張圖片的寬度壓縮成和列一樣寬,再按照同樣的壓縮比例對(duì)圖片的高度進(jìn)行壓縮,然后在這三列中找出當(dāng)前高度最小的一列,將圖片添加到這一列中。之后每當(dāng)需要添加一張新圖片時(shí),都去重復(fù)上面的操作,就會(huì)形成瀑布流格局的照片墻,示意圖如下所示。

201648235457901.png (347×459)

聽(tīng)我這么說(shuō)完后,你可能會(huì)覺(jué)得瀑布流的布局非常簡(jiǎn)單嘛,只需要使用三個(gè)LinearLayout平分整個(gè)屏幕寬度,然后動(dòng)態(tài)地addView()進(jìn)去就好了。確實(shí)如此,如果只是為了實(shí)現(xiàn)功能的話,就是這么簡(jiǎn)單??墒莿e忘了,我們是在手機(jī)上進(jìn)行開(kāi)發(fā),如果不停地往LinearLayout里添加圖片,程序很快就會(huì)OOM。因此我們還需要一個(gè)合理的方案來(lái)對(duì)圖片資源進(jìn)行釋放,這里仍然是準(zhǔn)備使用LruCache算法,這個(gè)具體的在文后會(huì)專(zhuān)門(mén)講,先知道是用這么回事~
下面我們就來(lái)開(kāi)始實(shí)現(xiàn)吧,新建一個(gè)Android項(xiàng)目,起名叫PhotoWallFallsDemo,并選擇4.0的API。
第一個(gè)要考慮的問(wèn)題是,我們到哪兒去收集這些大小參差不齊的圖片呢?這里我事先在百度上搜索了很多張風(fēng)景圖片,并且為了保證它們?cè)L問(wèn)的穩(wěn)定性,我將這些圖片都上傳到了我的CSDN相冊(cè)里,因此只要從這里下載圖片就可以了。新建一個(gè)Images類(lèi),將所有相冊(cè)中圖片的網(wǎng)址都配置進(jìn)去,代碼如下所示:

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" }; 
} 

然后新建一個(gè)ImageLoader類(lèi),用于方便對(duì)圖片進(jìn)行管理,代碼如下所示:

public class ImageLoader { 
 
 /** 
  * 圖片緩存技術(shù)的核心類(lèi),用于緩存所有下載好的圖片,在程序內(nèi)存達(dá)到設(shè)定值時(shí)會(huì)將最少最近使用的圖片移除掉。 
  */ 
 private static LruCache<String, Bitmap> mMemoryCache; 
 
 /** 
  * ImageLoader的實(shí)例。 
  */ 
 private static ImageLoader mImageLoader; 
 
 private ImageLoader() { 
  // 獲取應(yīng)用程序最大可用內(nèi)存 
  int maxMemory = (int) Runtime.getRuntime().maxMemory(); 
  int cacheSize = maxMemory / 8; 
  // 設(shè)置圖片緩存大小為程序最大可用內(nèi)存的1/8 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
   @Override 
   protected int sizeOf(String key, Bitmap bitmap) { 
    return bitmap.getByteCount(); 
   } 
  }; 
 } 
 
 /** 
  * 獲取ImageLoader的實(shí)例。 
  * 
  * @return ImageLoader的實(shí)例。 
  */ 
 public static ImageLoader getInstance() { 
  if (mImageLoader == null) { 
   mImageLoader = new ImageLoader(); 
  } 
  return mImageLoader; 
 } 
 
 /** 
  * 將一張圖片存儲(chǔ)到LruCache中。 
  * 
  * @param key 
  *   LruCache的鍵,這里傳入圖片的URL地址。 
  * @param bitmap 
  *   LruCache的鍵,這里傳入從網(wǎng)絡(luò)上下載的Bitmap對(duì)象。 
  */ 
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
  if (getBitmapFromMemoryCache(key) == null) { 
   mMemoryCache.put(key, bitmap); 
  } 
 } 
 
 /** 
  * 從LruCache中獲取一張圖片,如果不存在就返回null。 
  * 
  * @param key 
  *   LruCache的鍵,這里傳入圖片的URL地址。 
  * @return 對(duì)應(yīng)傳入鍵的Bitmap對(duì)象,或者null。 
  */ 
 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) { 
   // 計(jì)算出實(shí)際寬度和目標(biāo)寬度的比率 
   final int widthRatio = Math.round((float) width / (float) reqWidth); 
   inSampleSize = widthRatio; 
  } 
  return inSampleSize; 
 } 
 
 public static Bitmap decodeSampledBitmapFromResource(String pathName, 
   int reqWidth) { 
  // 第一次解析將inJustDecodeBounds設(shè)置為true,來(lái)獲取圖片大小 
  final BitmapFactory.Options options = new BitmapFactory.Options(); 
  options.inJustDecodeBounds = true; 
  BitmapFactory.decodeFile(pathName, options); 
  // 調(diào)用上面定義的方法計(jì)算inSampleSize值 
  options.inSampleSize = calculateInSampleSize(options, reqWidth); 
  // 使用獲取到的inSampleSize值再次解析圖片 
  options.inJustDecodeBounds = false; 
  return BitmapFactory.decodeFile(pathName, options); 
 } 
 
} 

這里我們將ImageLoader類(lèi)設(shè)成單例,并在構(gòu)造函數(shù)中初始化了LruCache類(lèi),把它的最大緩存容量設(shè)為最大可用內(nèi)存的1/8。然后又提供了其它幾個(gè)方法可以操作LruCache,以及對(duì)圖片進(jìn)行壓縮和讀取。
接下來(lái)新建MyScrollView繼承自ScrollView,代碼如下所示:

public class MyScrollView extends ScrollView implements OnTouchListener { 
 
 /** 
  * 每頁(yè)要加載的圖片數(shù)量 
  */ 
 public static final int PAGE_SIZE = 15; 
 
 /** 
  * 記錄當(dāng)前已加載到第幾頁(yè) 
  */ 
 private int page; 
 
 /** 
  * 每一列的寬度 
  */ 
 private int columnWidth; 
 
 /** 
  * 當(dāng)前第一列的高度 
  */ 
 private int firstColumnHeight; 
 
 /** 
  * 當(dāng)前第二列的高度 
  */ 
 private int secondColumnHeight; 
 
 /** 
  * 當(dāng)前第三列的高度 
  */ 
 private int thirdColumnHeight; 
 
 /** 
  * 是否已加載過(guò)一次layout,這里onLayout中的初始化只需加載一次 
  */ 
 private boolean loadOnce; 
 
 /** 
  * 對(duì)圖片進(jìn)行管理的工具類(lèi) 
  */ 
 private ImageLoader imageLoader; 
 
 /** 
  * 第一列的布局 
  */ 
 private LinearLayout firstColumn; 
 
 /** 
  * 第二列的布局 
  */ 
 private LinearLayout secondColumn; 
 
 /** 
  * 第三列的布局 
  */ 
 private LinearLayout thirdColumn; 
 
 /** 
  * 記錄所有正在下載或等待下載的任務(wù)。 
  */ 
 private static Set<LoadImageTask> taskCollection; 
 
 /** 
  * MyScrollView下的直接子布局。 
  */ 
 private static View scrollLayout; 
 
 /** 
  * MyScrollView布局的高度。 
  */ 
 private static int scrollViewHeight; 
 
 /** 
  * 記錄上垂直方向的滾動(dòng)距離。 
  */ 
 private static int lastScrollY = -1; 
 
 /** 
  * 記錄所有界面上的圖片,用以可以隨時(shí)控制對(duì)圖片的釋放。 
  */ 
 private List<ImageView> imageViewList = new ArrayList<ImageView>(); 
 
 /** 
  * 在Handler中進(jìn)行圖片可見(jiàn)性檢查的判斷,以及加載更多圖片的操作。 
  */ 
 private static Handler handler = new Handler() { 
 
  public void handleMessage(android.os.Message msg) { 
   MyScrollView myScrollView = (MyScrollView) msg.obj; 
   int scrollY = myScrollView.getScrollY(); 
   // 如果當(dāng)前的滾動(dòng)位置和上次相同,表示已停止?jié)L動(dòng) 
   if (scrollY == lastScrollY) { 
    // 當(dāng)滾動(dòng)的最底部,并且當(dāng)前沒(méi)有正在下載的任務(wù)時(shí),開(kāi)始加載下一頁(yè)的圖片 
    if (scrollViewHeight + scrollY >= scrollLayout.getHeight() 
      && taskCollection.isEmpty()) { 
     myScrollView.loadMoreImages(); 
    } 
    myScrollView.checkVisibility(); 
   } else { 
    lastScrollY = scrollY; 
    Message message = new Message(); 
    message.obj = myScrollView; 
    // 5毫秒后再次對(duì)滾動(dòng)位置進(jìn)行判斷 
    handler.sendMessageDelayed(message, 5); 
   } 
  }; 
 
 }; 
 
 /** 
  * MyScrollView的構(gòu)造函數(shù)。 
  * 
  * @param context 
  * @param attrs 
  */ 
 public MyScrollView(Context context, AttributeSet attrs) { 
  super(context, attrs); 
  imageLoader = ImageLoader.getInstance(); 
  taskCollection = new HashSet<LoadImageTask>(); 
  setOnTouchListener(this); 
 } 
 
 /** 
  * 進(jìn)行一些關(guān)鍵性的初始化操作,獲取MyScrollView的高度,以及得到第一列的寬度值。并在這里開(kāi)始加載第一頁(yè)的圖片。 
  */ 
 @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(); 
  } 
 } 
 
 /** 
  * 監(jiān)聽(tīng)用戶(hù)的觸屏事件,如果用戶(hù)手指離開(kāi)屏幕則開(kāi)始進(jìn)行滾動(dòng)檢測(cè)。 
  */ 
 @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; 
 } 
 
 /** 
  * 開(kāi)始加載下一頁(yè)的圖片,每張圖片都會(huì)開(kāi)啟一個(gè)異步線程去下載。 
  */ 
 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(), "已沒(méi)有更多圖片", Toast.LENGTH_SHORT) 
      .show(); 
   } 
  } else { 
   Toast.makeText(getContext(), "未發(fā)現(xiàn)SD卡", Toast.LENGTH_SHORT).show(); 
  } 
 } 
 
 /** 
  * 遍歷imageViewList中的每張圖片,對(duì)圖片的可見(jiàn)性進(jìn)行檢查,如果圖片已經(jīng)離開(kāi)屏幕可見(jiàn)范圍,則將圖片替換成一張空?qǐng)D。 
  */ 
 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); 
   } 
  } 
 } 
 
 /** 
  * 判斷手機(jī)是否有SD卡。 
  * 
  * @return 有SD卡返回true,沒(méi)有返回false。 
  */ 
 private boolean hasSDCard() { 
  return Environment.MEDIA_MOUNTED.equals(Environment 
    .getExternalStorageState()); 
 } 
 
 /** 
  * 異步下載圖片的任務(wù)。 
  * 
  * @author guolin 
  */ 
 class LoadImageTask extends AsyncTask<String, Void, Bitmap> { 
 
  /** 
   * 圖片的URL地址 
   */ 
  private String mImageUrl; 
 
  /** 
   * 可重復(fù)使用的ImageView 
   */ 
  private ImageView mImageView; 
 
  public LoadImageTask() { 
  } 
 
  /** 
   * 將可重復(fù)使用的ImageView傳入 
   * 
   * @param imageView 
   */ 
  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); 
  } 
 
  /** 
   * 根據(jù)傳入的URL,對(duì)圖片進(jìn)行加載。如果這張圖片已經(jīng)存在于SD卡中,則直接從SD卡里讀取,否則就從網(wǎng)絡(luò)上下載。 
   * 
   * @param imageUrl 
   *   圖片的URL地址 
   * @return 加載到內(nèi)存的圖片。 
   */ 
  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; 
  } 
 
  /** 
   * 向ImageView中添加一張圖片 
   * 
   * @param bitmap 
   *   待添加的圖片 
   * @param imageWidth 
   *   圖片的寬度 
   * @param imageHeight 
   *   圖片的高度 
   */ 
  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); 
   } 
  } 
 
  /** 
   * 找到此時(shí)應(yīng)該添加圖片的一列。原則就是對(duì)三列的高度進(jìn)行判斷,當(dāng)前高度最小的一列就是應(yīng)該添加的一列。 
   * 
   * @param imageView 
   * @param imageHeight 
   * @return 應(yīng)該添加圖片的一列 
   */ 
  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; 
   } 
  } 
 
  /** 
   * 將圖片下載到SD卡緩存起來(lái)。 
   * 
   * @param imageUrl 
   *   圖片的URL地址。 
   */ 
  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); 
    } 
   } 
  } 
 
  /** 
   * 獲取圖片的本地存儲(chǔ)路徑。 
   * 
   * @param imageUrl 
   *   圖片的URL地址。 
   * @return 圖片的本地存儲(chǔ)路徑。 
   */ 
  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是實(shí)現(xiàn)瀑布流照片墻的核心類(lèi),這里我來(lái)重點(diǎn)給大家介紹一下。首先它是繼承自ScrollView的,這樣就允許用戶(hù)可以通過(guò)滾動(dòng)的方式來(lái)瀏覽更多的圖片。這里提供了一個(gè)loadMoreImages()方法,是專(zhuān)門(mén)用于加載下一頁(yè)的圖片的,因此在onLayout()方法中我們要先調(diào)用一次這個(gè)方法,以初始化第一頁(yè)的圖片。然后在onTouch方法中每當(dāng)監(jiān)聽(tīng)到手指離開(kāi)屏幕的事件,就會(huì)通過(guò)一個(gè)handler來(lái)對(duì)當(dāng)前ScrollView的滾動(dòng)狀態(tài)進(jìn)行判斷,如果發(fā)現(xiàn)已經(jīng)滾動(dòng)到了最底部,就會(huì)再次調(diào)用loadMoreImages()方法去加載下一頁(yè)的圖片。
那我們就要來(lái)看一看loadMoreImages()方法的內(nèi)部細(xì)節(jié)了。在這個(gè)方法中,使用了一個(gè)循環(huán)來(lái)加載這一頁(yè)中的每一張圖片,每次都會(huì)開(kāi)啟一個(gè)LoadImageTask,用于對(duì)圖片進(jìn)行異步加載。然后在LoadImageTask中,首先會(huì)先檢查一下這張圖片是不是已經(jīng)存在于SD卡中了,如果還沒(méi)存在,就從網(wǎng)絡(luò)上下載,然后把這張圖片存放在LruCache中。接著將這張圖按照一定的比例進(jìn)行壓縮,并找出當(dāng)前高度最小的一列,把壓縮后的圖片添加進(jìn)去就可以了。
另外,為了保證照片墻上的圖片都能夠合適地被回收,這里還加入了一個(gè)可見(jiàn)性檢查的方法,即checkVisibility()方法。這個(gè)方法的核心思想就是檢查目前照片墻上的所有圖片,判斷出哪些是可見(jiàn)的,哪些是不可見(jiàn)。然后將那些不可見(jiàn)的圖片都替換成一張空?qǐng)D,這樣就可以保證程序始終不會(huì)占用過(guò)高的內(nèi)存。當(dāng)這些圖片又重新變?yōu)榭梢?jiàn)的時(shí)候,只需要再?gòu)腖ruCache中將這些圖片重新取出即可。如果某張圖片已經(jīng)從LruCache中被移除了,就會(huì)開(kāi)啟一個(gè)LoadImageTask,將這張圖片重新加載到內(nèi)存中。
然后打開(kāi)或新建activity_main.xml,在里面設(shè)置好瀑布流的布局方式,如下所示:

<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>

 
可以看到,這里我們使用了剛才編寫(xiě)好的MyScrollView作為根布局,然后在里面放入了一個(gè)直接子布局LinearLayout用于統(tǒng)計(jì)當(dāng)前滑動(dòng)布局的高度,然后在這個(gè)布局下又添加了三個(gè)等寬的LinearLayout分別作為第一列、第二列和第三列的布局,這樣在MyScrollView中就可以動(dòng)態(tài)地向這三個(gè)LinearLayout里添加圖片了。
最后,由于我們使用到了網(wǎng)絡(luò)和SD卡存儲(chǔ)的功能,因此還需要在AndroidManifest.xml中添加以下權(quán)限:

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

這樣我們所有的編碼工作就已經(jīng)完成了,現(xiàn)在可以嘗試運(yùn)行一下,效果如下圖所示:

201648235542999.gif (235×418)

LruCache圖片緩存技術(shù)
在你應(yīng)用程序的UI界面加載一張圖片是一件很簡(jiǎn)單的事情,但是當(dāng)你需要在界面上加載一大堆圖片的時(shí)候,情況就變得復(fù)雜起來(lái)。在很多情況下,(比如使用ListView, GridView 或者 ViewPager 這樣的組件),屏幕上顯示的圖片可以通過(guò)滑動(dòng)屏幕等事件不斷地增加,最終導(dǎo)致OOM。
為了保證內(nèi)存的使用始終維持在一個(gè)合理的范圍,通常會(huì)把被移除屏幕的圖片進(jìn)行回收處理。此時(shí)垃圾回收器也會(huì)認(rèn)為你不再持有這些圖片的引用,從而對(duì)這些圖片進(jìn)行GC操作。用這種思路來(lái)解決問(wèn)題是非常好的,可是為了能讓程序快速運(yùn)行,在界面上迅速地加載圖片,你又必須要考慮到某些圖片被回收之后,用戶(hù)又將它重新滑入屏幕這種情況。這時(shí)重新去加載一遍剛剛加載過(guò)的圖片無(wú)疑是性能的瓶頸,你需要想辦法去避免這個(gè)情況的發(fā)生。
這個(gè)時(shí)候,使用內(nèi)存緩存技術(shù)可以很好的解決這個(gè)問(wèn)題,它可以讓組件快速地重新加載和處理圖片。下面我們就來(lái)看一看如何使用內(nèi)存緩存技術(shù)來(lái)對(duì)圖片進(jìn)行緩存,從而讓你的應(yīng)用程序在加載很多圖片的時(shí)候可以提高響應(yīng)速度和流暢性。
內(nèi)存緩存技術(shù)對(duì)那些大量占用應(yīng)用程序?qū)氋F內(nèi)存的圖片提供了快速訪問(wèn)的方法。其中最核心的類(lèi)是LruCache (此類(lèi)在android-support-v4的包中提供) 。這個(gè)類(lèi)非常適合用來(lái)緩存圖片,它的主要算法原理是把最近使用的對(duì)象用強(qiáng)引用存儲(chǔ)在 LinkedHashMap 中,并且把最近最少使用的對(duì)象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除。
在過(guò)去,我們經(jīng)常會(huì)使用一種非常流行的內(nèi)存緩存技術(shù)的實(shí)現(xiàn),即軟引用或弱引用 (SoftReference or WeakReference)。但是現(xiàn)在已經(jīng)不再推薦使用這種方式了,因?yàn)閺?Android 2.3 (API Level 9)開(kāi)始,垃圾回收器會(huì)更傾向于回收持有軟引用或弱引用的對(duì)象,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的數(shù)據(jù)會(huì)存儲(chǔ)在本地的內(nèi)存當(dāng)中,因而無(wú)法用一種可預(yù)見(jiàn)的方式將其釋放,這就有潛在的風(fēng)險(xiǎn)造成應(yīng)用程序的內(nèi)存溢出并崩潰。
為了能夠選擇一個(gè)合適的緩存大小給LruCache, 有以下多個(gè)因素應(yīng)該放入考慮范圍內(nèi),例如:
你的設(shè)備可以為每個(gè)應(yīng)用程序分配多大的內(nèi)存?
設(shè)備屏幕上一次最多能顯示多少?gòu)垐D片?有多少圖片需要進(jìn)行預(yù)加載,因?yàn)橛锌赡芎芸煲矔?huì)顯示在屏幕上?
你的設(shè)備的屏幕大小和分辨率分別是多少?一個(gè)超高分辨率的設(shè)備(例如 Galaxy Nexus) 比起一個(gè)較低分辨率的設(shè)備(例如 Nexus S),在持有相同數(shù)量圖片的時(shí)候,需要更大的緩存空間。
圖片的尺寸和大小,還有每張圖片會(huì)占據(jù)多少內(nèi)存空間。
圖片被訪問(wèn)的頻率有多高?會(huì)不會(huì)有一些圖片的訪問(wèn)頻率比其它圖片要高?如果有的話,你也許應(yīng)該讓一些圖片常駐在內(nèi)存當(dāng)中,或者使用多個(gè)LruCache 對(duì)象來(lái)區(qū)分不同組的圖片。
你能維持好數(shù)量和質(zhì)量之間的平衡嗎?有些時(shí)候,存儲(chǔ)多個(gè)低像素的圖片,而在后臺(tái)去開(kāi)線程加載高像素的圖片會(huì)更加的有效。
并沒(méi)有一個(gè)指定的緩存大小可以滿(mǎn)足所有的應(yīng)用程序,這是由你決定的。你應(yīng)該去分析程序內(nèi)存的使用情況,然后制定出一個(gè)合適的解決方案。一個(gè)太小的緩存空間,有可能造成圖片頻繁地被釋放和重新加載,這并沒(méi)有好處。而一個(gè)太大的緩存空間,則有可能還是會(huì)引起 java.lang.OutOfMemory 的異常。
下面是一個(gè)使用 LruCache 來(lái)緩存圖片的例子:

private LruCache<String, Bitmap> mMemoryCache; 
 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
  // 獲取到可用內(nèi)存的最大值,使用內(nèi)存超出這個(gè)值會(huì)引起OutOfMemory異常。 
  // LruCache通過(guò)構(gòu)造函數(shù)傳入緩存值,以KB為單位。 
  int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
  // 使用最大可用內(nèi)存值的1/8作為緩存的大小。 
  int cacheSize = maxMemory / 8; 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
    @Override 
    protected int sizeOf(String key, Bitmap bitmap) { 
      // 重寫(xiě)此方法來(lái)衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量。 
      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); 
} 

在這個(gè)例子當(dāng)中,使用了系統(tǒng)分配給應(yīng)用程序的八分之一內(nèi)存來(lái)作為緩存大小。在中高配置的手機(jī)當(dāng)中,這大概會(huì)有4兆(32/8)的緩存空間。一個(gè)全屏幕的 GridView 使用4張 800x480分辨率的圖片來(lái)填充,則大概會(huì)占用1.5兆的空間(800*480*4)。因此,這個(gè)緩存大小可以存儲(chǔ)2.5頁(yè)的圖片。
當(dāng)向 ImageView 中加載一張圖片時(shí),首先會(huì)在 LruCache 的緩存中進(jìn)行檢查。如果找到了相應(yīng)的鍵值,則會(huì)立刻更新ImageView ,否則開(kāi)啟一個(gè)后臺(tái)線程來(lái)加載這張圖片。

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 還要把新加載的圖片的鍵值對(duì)放到緩存中。

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

掌握了以上兩種方法,不管是要在程序中加載超大圖片,還是要加載大量圖片,都不用擔(dān)心OOM的問(wèn)題了!

相關(guān)文章

  • Android開(kāi)發(fā)App啟動(dòng)流程與消息機(jī)制詳解

    Android開(kāi)發(fā)App啟動(dòng)流程與消息機(jī)制詳解

    這篇文章主要為大家介紹了Android開(kāi)發(fā)App啟動(dòng)流程與消息機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 淺析Android中build.gradle的實(shí)用技巧

    淺析Android中build.gradle的實(shí)用技巧

    這篇文章主要介紹了淺析Android中build.gradle的實(shí)用技巧,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-03-03
  • Android開(kāi)發(fā)之超強(qiáng)圖片工具類(lèi)BitmapUtil完整實(shí)例

    Android開(kāi)發(fā)之超強(qiáng)圖片工具類(lèi)BitmapUtil完整實(shí)例

    這篇文章主要介紹了Android開(kāi)發(fā)之超強(qiáng)圖片工具類(lèi)BitmapUtil,結(jié)合完整實(shí)例形式分析了Android圖片的常用操作技巧,包括圖片的加載、轉(zhuǎn)換、縮放、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下
    2017-11-11
  • Kotlin List與Set和Map實(shí)例講解

    Kotlin List與Set和Map實(shí)例講解

    集合是可變數(shù)量(可能為0)的一組條目,kotlin標(biāo)準(zhǔn)庫(kù)提供一個(gè)整套用于集合管理的工具,各種集合對(duì)于解決問(wèn)題都具有重要意義,并且經(jīng)常用到。kotlin中的集合與Java基本類(lèi)似
    2022-10-10
  • Android Intent的幾種用法詳細(xì)解析

    Android Intent的幾種用法詳細(xì)解析

    這篇文章主要介紹了Android Intent的幾種用法,有需要的朋友可以參考一下
    2014-01-01
  • 詳解Android 藍(lán)牙通信方式總結(jié)

    詳解Android 藍(lán)牙通信方式總結(jié)

    這篇文章主要介紹了詳解Android 藍(lán)牙通信方式總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2013-11-11
  • kotlin中EditText賦值Type mismatch方式

    kotlin中EditText賦值Type mismatch方式

    這篇文章主要介紹了kotlin中EditText賦值Type mismatch方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-03-03
  • 詳解Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單)

    詳解Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單)

    這篇文章主要介紹了Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Android編程自定義搜索框?qū)崿F(xiàn)方法【附demo源碼下載】

    Android編程自定義搜索框?qū)崿F(xiàn)方法【附demo源碼下載】

    這篇文章主要介紹了Android編程自定義搜索框?qū)崿F(xiàn)方法,涉及Android界面布局、數(shù)據(jù)加載、事件響應(yīng)等相關(guān)操作技巧,并附帶完整demo源碼供讀者下載參考,需要的朋友可以參考下
    2017-12-12
  • Android中如何實(shí)現(xiàn)清空搜索框的文字

    Android中如何實(shí)現(xiàn)清空搜索框的文字

    本文主要介紹Android中實(shí)現(xiàn)清空搜索框的文字的方法。項(xiàng)目中的有關(guān)搜索的地方,加上清空文字的功能,目的是為了增加用戶(hù)體驗(yàn),使用戶(hù)刪除文本更加快捷。需要的朋友一起來(lái)看下吧
    2016-12-12

最新評(píng)論