4

我一直在訪問堆棧溢出多年,這是我第一次,我找不到任何帖子,可以解決我的問題(至少我沒有看到任何) 。懶惰加載GridView與從互聯網下載圖像

我有一個GridView與自定義適配器,我已覆蓋返回由ImageViewTextView作出的自定義視圖。

我從JSON解析的網址與他們的AsyncTask,存儲所有信息到ArrayListdoInBackground()方法,並在onPostExecute()方法調用notifyDataSetChanged()後,裝載圖像。一切安好。

現在我的問題是,當我啓動活動時,需要5-10秒的時間才能創建網格視圖並將其自身呈現給實體中的用戶。我想知道是否有方法顯示網格視圖與文字信息,然後每個圖像將加載。這是否可能,因爲它們都是用相同的方法創建的?

@Override 
public View getView(int arg0, View arg1, ViewGroup arg2) { 
    View v = null; 
    if (arg1 == null) { 
     LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
       Context.LAYOUT_INFLATER_SERVICE); 
     v = inflater.inflate(R.layout.custom_product_view, null); 
    } else { 
     v = arg1; 
    } 

    iv = (ImageView) v.findViewById(R.id.product_image); 
    imageLoader.DisplayImage(products.get(arg0).getImage(), iv); 

    TextView tv = (TextView) v.findViewById(R.id.product_price); 
    tv.setText(products.get(arg0).getPrice()); 

    return v; 
} 

我還必須告訴你,你可以從DisplayImage()的方法,我已經實現了這個懶加載看到:Lazy load of images in ListView。它工作正常,但事情是它再次加載整個視圖。我想要做的是啓動活動,首先加載標題,然後在完成下載時加載圖像。在這裏使用這段代碼,它只是懶加載整個視圖,包含網格視圖的每個單元格。我賺了幾秒鐘,因爲我不像以前一樣下載所有的圖像,但仍然不是我正在尋找的。

非常感謝。

回答

4

按照這種方法。

首先,創建一個自定義WebImageView類,如下所示。

public class WebImageView extends ImageView { 

    private Drawable placeholder, image; 

    public WebImageView(Context context) { 
     super(context); 
    } 
    public WebImageView(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
    } 
    public WebImageView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    public void setPlaceholderImage(Drawable drawable) { 
     placeholder = drawable; 
     if (image == null) { 
      setImageDrawable(placeholder); 
     } 
    } 
    public void setPlaceholderImage(int resid) { 
     placeholder = getResources().getDrawable(resid); 
     if (image == null) { 
      setImageDrawable(placeholder); 
     } 
    } 

    public void setImageUrl(String url) { 
     DownloadTask task = new DownloadTask(); 
     task.execute(url); 
    } 

    private class DownloadTask extends AsyncTask<String, Void, Bitmap> { 

     @Override 
     protected Bitmap doInBackground(String... params) { 
      String url = params[0]; 
      try { 
       URLConnection conn = (new URL(url)).openConnection(); 
       InputStream is = conn.getInputStream(); 
       BufferedInputStream bis = new BufferedInputStream(is); 

       ByteArrayBuffer baf = new ByteArrayBuffer(50); 
       int current = 0; 
       while ((current=bis.read()) != -1) { 
        baf.append((byte)current); 
       } 

       byte[] imageData = baf.toByteArray(); 
       return BitmapFactory.decodeByteArray(imageData, 0, imageData.length); 

      } catch (Exception e) { 
       return null; 
      } 
     } 

     @Override 
     protected void onPostExecute(Bitmap result) { 
      image = new BitmapDrawable(result); 
      if (image != null) { 
       setImageDrawable(image); 
      } 
     } 
    } 
} 

接下來,在活動中使用上述自定義的ImageView如下:

protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 

    WebImageView imageView = (WebImageView) findViewById(R.id.webimage); 
    imageView.setPlaceholderImage(R.drawable.ic_launcher); 
    imageView.setImageUrl("http://www.google.co.in/images/srpr/logo3w.png"); 
} 

總之,要設置一個佔位符圖像,其獲取的實際圖像下載完成時更換的ImageView 。所以GridView將立即渲染。

實施細則: 所以在你的自定義視圖(圖像+文字),而不是使用一個簡單的ImageView,使用WebImageView如上圖所示。當您獲取JSON響應時,將帶有標題的TextView和帶有圖像url的WebImageView一起設置。 因此,標題將立即顯示,圖像將加載延遲。

+0

是的,但又如何解釋如何加載文本? – AbuJaFaR 2013-03-25 13:48:47

+0

我的理解是,你正在調用一些REST API,然後解析JSON響應以獲取圖像URL及其標題。所以標題和圖像都會有滯後。我提到的方法是有一個佔位符文本以及圖像,以便GridView得到渲染。然後像我的例子中那樣異步更新標題和圖像,或者您爲圖像完成的方式。 – appsroxcom 2013-03-25 15:20:03

+0

我明白了。好的,謝謝,我會試一試! – AbuJaFaR 2013-03-25 15:29:06

1

我已經使用下面的類來實現圖像的延遲加載,它對我來說非常棒。你也嘗試一下。

ImageLoader的

/** 
     * This is class for display image in lazy-loading way. 
     */ 
    public class ImageLoader 
    { 
private static final String TAG = ImageLoader.class.getSimpleName(); 
private InputStream m_is = null; 
private OutputStream m_os = null; 
private Bitmap m_bitmap = null; 
private String m_imagePath; 
private File m_cacheDir; 
private WeakHashMap<String, Bitmap> m_cache = new WeakHashMap<String, Bitmap>(); 
/** 
* Makes the background thread low priority. This way it will not affect the 
* UI performance.<br> 
* Checks the Device SD card exits or not and assign path according this 
* condition. 
* 
* @param p_context 
*   activity context 
*/ 
public ImageLoader(Context p_context) 
{ 
    /** 
    * Make the background thread low priority. This way it will not affect 
    * the UI performance 
    */ 
    m_imageLoaderThread.setPriority(Thread.NORM_PRIORITY - 1); 
    /** 
    * Check the Device SD card exits or not and assign path according this 
    * condition. 
    */ 
    if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) 
    { 
     m_imagePath = Environment.getExternalStorageDirectory() + "/Android/data/" + p_context.getPackageName(); 
     m_cacheDir = new File(m_imagePath); 
    } 
    else 
    { 
     m_cacheDir = new File(p_context.getDir("Cache", Context.MODE_PRIVATE), "Cache"); 
    } 
    if (!m_cacheDir.exists()) 
     m_cacheDir.mkdirs(); 
} 
/** 
* Check Image exits on HashMap or not.If exist then set image to ImageView 
* else send request in the queue. 
* 
* @param p_url 
*   image Url 
* @param p_imageView 
*   image container 
* @param p_prgBar 
*   progressbar that is displayed till image is not download from 
*   server. 
*/ 
public void DisplayImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException 
{ 
    if (m_cache.containsKey(p_url)) 
    { 
     p_prgBar.setVisibility(View.GONE); 
     p_imageView.setVisibility(View.VISIBLE); 
     p_imageView.setImageBitmap(m_cache.get(p_url)); 
    } 
    else 
    { 
     queueImage(p_url, p_imageView, p_prgBar); 
    } 
} 
/** 
* Clear old task from the queue and add new image downloading in the queue. 
* 
* @param p_url 
*   image Url 
* @param p_imageView 
*   image container 
* @param p_prgBar 
*   progressbar that is displayed till image is not download from 
*   server. 
*/ 
private void queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) throws CustomException 
{ 
    try 
    { 
     m_imagesQueue.Clean(p_imageView); 
     ImageToLoad m_photoObj = new ImageToLoad(p_url, p_imageView, p_prgBar); 
     synchronized (m_imagesQueue.m_imagesToLoad) 
     { 
      m_imagesQueue.m_imagesToLoad.push(m_photoObj); 
      m_imagesQueue.m_imagesToLoad.notifyAll(); 
     } 
     /** 
     * start thread if it's not started yet 
     */ 
     if (m_imageLoaderThread.getState() == Thread.State.NEW) 
      m_imageLoaderThread.start(); 
    } 
    catch (CustomException c) 
    { 
     throw c; 
    } 
    catch (Throwable t) 
    { 
     CustomLogHandler.printErrorlog(t); 
     throw new CustomException(TAG + " Error in queueImage(String p_url, ImageView p_imageView, ProgressBar p_prgBar) of ImageLoader", t); 
    } 
} 
/** 
* Checks in SD card for cached file.If bitmap is not available then will 
* download it from Url. 
* 
* @param p_url 
*   imgae Url 
* @return bitmap from Cache or from server. 
*/ 
private Bitmap getBitmap(String p_url) throws CustomException 
{ 
    System.gc(); 
    String m_fileName = String.valueOf(p_url.hashCode()); 
    File m_file = new File(m_cacheDir, m_fileName); 
    // from SD cache 
    m_bitmap = decodeFile(m_file); 
    if (m_bitmap != null) 
     return m_bitmap; 
    // from web 
    try 
    { 
     Bitmap m_bitmap = null; 
     int m_connectionCode = 0; 
     m_connectionCode = HttpConnection.getHttpUrlConnection(p_url).getResponseCode(); 
     if (m_connectionCode == HttpURLConnection.HTTP_OK) 
     { 
      m_is = new URL(p_url).openStream(); 
      m_os = new FileOutputStream(m_file); 
      FileIO.copyStream(m_is, m_os); 
      m_os.close(); 
      m_os = null; 
      m_bitmap = decodeFile(m_file); 
      m_is.close(); 
      m_is = null; 
      HttpConnection.getHttpUrlConnection(p_url).disconnect(); 
     } 
     return m_bitmap; 
    } 
    catch (CustomException c) 
    { 
     throw c; 
    } 
    catch (Throwable t) 
    { 
     CustomLogHandler.printErrorlog(t); 
     throw new CustomException(TAG + " Error in getBitmap(String p_url) of ImageLoader", t); 
    } 
} 
/** 
* Decodes the Image file to bitmap. 
* 
* @param p_file 
*   Image file object 
* @return decoded bitmap 
*/ 
private Bitmap decodeFile(File p_file) throws CustomException 
{ 
    try 
    { 
     // decode image size 
     Bitmap m_retBmp = null; 
     System.gc(); 
     int m_scale = 1; 
     if (p_file.length() > 400000) 
     { 
      m_scale = 4; 
     } 
     else if (p_file.length() > 100000 && p_file.length() < 400000) 
     { 
      m_scale = 3; 
     } 
     // decode with inSampleSize 
     if (p_file.exists()) 
     { 
      BitmapFactory.Options m_o2 = new BitmapFactory.Options(); 
      m_o2.inSampleSize = m_scale; 
      m_retBmp = BitmapFactory.decodeFile(p_file.getPath(), m_o2); 
     } 
     return m_retBmp; 
    } 
    catch (Throwable t) 
    { 
     CustomLogHandler.printErrorlog(t); 
     throw new CustomException(TAG + " Error in decodeFile(File p_file) of ImageLoader", t); 
    } 
} 
/** 
* Stores image information 
*/ 
private class ImageToLoad 
{ 
    public String m_url; 
    public ImageView m_imageView; 
    public ProgressBar m_prgBar; 
    public ImageToLoad(String p_str, ImageView p_img, ProgressBar p_prgBar) 
    { 
     m_url = p_str; 
     m_imageView = p_img; 
     m_imageView.setTag(p_str); 
     m_prgBar = p_prgBar; 
    } 
} 
ImagesQueue m_imagesQueue = new ImagesQueue(); 
/** 
* This is method to stop current running thread. 
*/ 
public void stopThread() 
{ 
    m_imageLoaderThread.interrupt(); 
} 
/** 
* Stores list of image to be downloaded in stack. 
*/ 
class ImagesQueue 
{ 
    private Stack<ImageToLoad> m_imagesToLoad = new Stack<ImageToLoad>(); 
    /** 
    * Removes all instances of this ImageView 
    * 
    * @param p_ivImage 
    *   imageView 
    */ 
    public void Clean(ImageView p_ivImage) throws CustomException 
    { 
     try 
     { 
      for (int m_i = 0; m_i < m_imagesToLoad.size();) 
      { 
       if (m_imagesToLoad.get(m_i).m_imageView == p_ivImage) 
        m_imagesToLoad.remove(m_i); 
       else 
        m_i++; 
      } 
     } 
     catch (Throwable t) 
     { 
      CustomLogHandler.printErrorlog(t); 
      throw new CustomException(TAG + " Error in Clean(ImageView p_image) of ImageLoader", t); 
     } 
    } 
} 
/** 
* 
* This is class waits until there are any images to load in the queue. 
*/ 
class ImagesLoader extends Thread 
{ 
    public void run() 
    { 
     try 
     { 
      while (true) 
      { 
       if (m_imagesQueue.m_imagesToLoad.size() == 0) 
        synchronized (m_imagesQueue.m_imagesToLoad) 
        { 
         m_imagesQueue.m_imagesToLoad.wait(); 
        } 
       if (m_imagesQueue.m_imagesToLoad.size() != 0) 
       { 
        ImageToLoad m_imageToLoadObj; 
        synchronized (m_imagesQueue.m_imagesToLoad) 
        { 
         m_imageToLoadObj = m_imagesQueue.m_imagesToLoad.pop(); 
        } 
        Bitmap m_bmp = getBitmap(m_imageToLoadObj.m_url); 
        m_cache.put(m_imageToLoadObj.m_url, m_bmp); 
        if (((String) m_imageToLoadObj.m_imageView.getTag()).equals(m_imageToLoadObj.m_url)) 
        { 
         BitmapDisplayer m_bmpdisplayer = new BitmapDisplayer(m_bmp, m_imageToLoadObj.m_imageView, m_imageToLoadObj.m_prgBar); 
         Activity m_activity = (Activity) m_imageToLoadObj.m_imageView.getContext(); 
         m_activity.runOnUiThread(m_bmpdisplayer); 
        } 
       } 
       if (Thread.interrupted()) 
        break; 
      } 
     } 
     catch (InterruptedException e) 
     { 
      /* 
      * allow thread to exit 
      */ 
     } 
     catch (Throwable t) 
     { 
      CustomLogHandler.printErrorlog(t); 
     } 
    } 
} 
ImagesLoader m_imageLoaderThread = new ImagesLoader(); 
/** 
* This class Used to display bitmap in the UI thread 
*/ 
class BitmapDisplayer implements Runnable 
{ 
    Bitmap m_bmp; 
    ImageView m_imageView; 
    ProgressBar m_prgBar; 
    public BitmapDisplayer(Bitmap p_bmp, ImageView p_imgview, ProgressBar p_prgBar) 
    { 
     m_bmp = p_bmp; 
     m_imageView = p_imgview; 
     m_prgBar = p_prgBar; 
    } 
    public void run() 
    { 
     if (m_bmp != null) 
     { 
      m_imageView.setImageBitmap(m_bmp); 
      m_prgBar.setVisibility(View.GONE); 
      m_imageView.setVisibility(View.VISIBLE); 
     } 
    } 
} 
    } 

使用上面的類如下:

首先,你需要把ProgressBar在你有如下的ImageView您的自定義佈局:

<RelativeLayout 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:id="@+id/RelativeImagelayout"> 
     <ProgressBar android:id="@+id/Progress" 
       android:layout_height="wrap_content" 
       android:layout_width="wrap_content" 
       android:layout_marginTop="10dp"/> 
    <ImageView 
     android:id="@+id/ivImage" 
     android:layout_width="80dp" 
     android:layout_height="90dp" 
     android:layout_marginTop="10dp" 
     android:clickable="false"/> 
</RelativeLayout> 

在您的適配器類創建ImageLoader類的實例,並在您的getView方法如下使用它:

ImageView m_ibImage = (ImageView) v.findViewById(R.id.ivImage); 
ProgressBar m_pbProgress = (ProgressBar) v.findViewById(R.id.Progress); 
     if (products.get(arg0).getImage().toString().equals(null) 
       || products.get(arg0).getImage().toString().equals("")) 
     { 
      m_pbProgress.setVisibility(View.INVISIBLE); 
      m_ibImage.setVisibility(View.VISIBLE); 
     } 
     else if (!products.get(arg0).getImage().toString().equals(null)) 
     { 
      m_imgLoader.DisplayImage(products.get(arg0).getImage(), m_ibImage, 
        m_pbProgress); 
     } 

我希望這會幫助你。

謝謝

+0

我不確定你是否理解我的問題。我想先加載TextView,然後在相同的自定義視圖中加載ImageView。圖片的延遲加載對我有用,我沒有問題。謝謝。 – AbuJaFaR 2013-03-25 13:37:30

0

你提到的答案是不是好,在我看來。例如,如果您有50張圖片,當用戶向上/向下滾動整個列表時,該示例項目將生成50個線程。這對手機等移動設備不利。一個側面說明,他的概念「lazy list」與Android SDK定義的概念不同。對於延遲加載列表視圖的示例代碼,看看:

[Android SDK]/samples/android-x/ApiDemos/src/com/example/android/apis/view/List13.java 

其中x是API級別。您可以在任何模擬器中測試已編譯的應用程序,打開應用程序API演示>視圖>列表> 13.緩慢適配器

關於您目前的方法。您不應使用AsyncTask下載圖像。該documentation說:

AsyncTasks應該理想地(最多幾秒鐘)可用於短期操作

您應該改爲:

  • 使用service下載圖像背景。請注意,服務在主UI線程上運行,因此爲了避免NetworkOnMainThreadException,您需要在您的服務中使用類似Thread的內容。使用content provider來管理SD卡上下載的圖像。例如,您將原始URL的地圖保存爲相應的下載文件。
  • 與內容提供商一起,您的網格視圖使用CursorAdapter,而使用網格視圖的活動/片段使用loaders

基本上,用戶在第一次打開活動時,會創建新適配器並將其設置爲網格視圖。所以它與內容提供商有聯繫。然後你開始檢查並下載圖像。對於下載的每個圖像,您將其插入內容提供商。提供者通知任何觀察者有關更改 - 您的活動/片段(加載器)接收通知並更新UI。