2010-09-21 16 views
3

Je veux mettre un TextView avec SpannableString qui est de la méthode ci-dessous:Afficher des images sur Android en utilisant TextView et Html.ImageGetter de manière asynchrone?

Html.fromHtml(String source, Html.ImageGetter imageGetter, 
    Html.TagHandler tagHandler) 

Mais le ImageGetter ici besoin de passer outre la méthode ci-dessous:

public abstract Drawable getDrawable(String source) 

Parce que je dois obtenir le drawable de Internet, je dois le faire de manière asynchrone et semble que ce n'est pas le cas.

Comment le faire fonctionner? Merci.

Répondre

4

Maintenant, je suis en utilisant un AsyncTask pour télécharger les images dans le ImageGetter:

Spanned spannedContent = Html.fromHtml(htmlString, new ImageGetter() { 

     @Override 
     public Drawable getDrawable(String source) { 
      new ImageDownloadAsyncTask().execute(textView, htmlString, source); 
      return null; 
     } 
    }, null); 

et définir le texte à nouveau dans le TextView lorsque l'image a été téléchargée.

Maintenant ça marche. Mais il a échoué lorsque j'ai essayé de faire le TextView.postInvalidate() pour redessiner les images téléchargées. Je dois à nouveau faire setText() dans le AsyncTask.

Est-ce que quelqu'un sait pourquoi?

+0

Pouvez-vous partager votre code? Votre solution semble vraiment interessante ... –

+0

Salut, je viens de redéfinir le texte avec le contenu Spanned. Cela déclenchera le ImageGetter à nouveau. – shiami

+0

Je parlais de ImageDownloadAsyncTask (textView, htmlString, source) pourquoi a-t-il besoin de htmlString asparameter? –

4

Voici mon code qui saisit toutes les images dans la chaîne html (il est simplifié de l'original, donc j'espère que cela fonctionne):

private HashMap<String, Drawable> mImageCache = new HashMap<String, Drawable>(); 
private String mDescription = "...your html here..."; 

private void updateImages(final boolean downloadImages) { 
    if (mDescription == null) return; 
    Spanned spanned = Html.fromHtml(mDescription, 
     new Html.ImageGetter() { 
     @Override 
     public Drawable getDrawable(final String source) { 
      Drawable drawable = mImageCache.get(source); 
      if (drawable != null) { 
       return drawable; 
      } else if (downloadImages) { 
       new ImageDownloader(new ImageDownloader.ImageDownloadListener() { 
        @Override 
        public void onImageDownloadComplete(byte[] bitmapData) { 
         Drawable drawable = new BitmapDrawable(getResources(), 
           BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length)); 
         try { 
          drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 
         } catch (Exception ex) {} 
         mImageCache.put(source, drawable); 
         updateImages(false); 
        } 
        @Override 
        public void onImageDownloadFailed(Exception ex) {} 
       }).execute(source); 
      } 
      return null; 
     } 
    }, null); 
    tvDescription.setText(spanned); 
} 

Donc, fondamentalement, ce qui se passe ici est le ImageGetter fera une demande pour chaque image dans la description html. Si cette image ne figure pas dans le tableau mImageCache et que downloadImages est true, nous exécutons une tâche asynchrone pour télécharger cette image. Une fois terminé, nous ajoutons le drawable dans la hashmap, puis nous faisons à nouveau appel à cette méthode (mais avec downloadImages comme faux, donc nous ne risquons pas une boucle infinie), où l'image pourra être saisie avec le deuxième essai.

Et avec cela, vous aurez besoin de la classe ImageDownloader que je:

public class ImageDownloader extends AsyncTask { 
    public interface ImageDownloadListener { 
     public void onImageDownloadComplete(byte[] bitmapData); 
     public void onImageDownloadFailed(Exception ex); 
    } 

    private ImageDownloadListener mListener = null; 

    public ImageDownloader(ImageDownloadListener listener) { 
     mListener = listener; 
    } 

    protected Object doInBackground(Object... urls) { 
     String url = (String)urls[0]; 
     ByteArrayOutputStream baos = null; 
     InputStream mIn = null; 
     try { 
      mIn = new java.net.URL(url).openStream(); 
      int bytesRead; 
      byte[] buffer = new byte[64]; 
      baos = new ByteArrayOutputStream(); 
      while ((bytesRead = mIn.read(buffer)) > 0) { 
       if (isCancelled()) return null; 
       baos.write(buffer, 0, bytesRead); 
      } 
      return new AsyncTaskResult<byte[]>(baos.toByteArray()); 

     } catch (Exception ex) { 
      return new AsyncTaskResult<byte[]>(ex); 
     } 
     finally { 
      Quick.close(mIn); 
      Quick.close(baos); 
     } 
    } 

    protected void onPostExecute(Object objResult) { 
     AsyncTaskResult<byte[]> result = (AsyncTaskResult<byte[]>)objResult; 
     if (isCancelled() || result == null) return; 
     if (result.getError() != null) { 
      mListener.onImageDownloadFailed(result.getError()); 
     } 
     else if (mListener != null) 
      mListener.onImageDownloadComplete(result.getResult()); 
    } 
} 
+0

Salut, mon idée me semble la même. Vous devez re-setText après avoir fini de télécharger l'image. – shiami

+0

Merci pour ce code-extrait. Si vous souhaitez couvrir plusieurs tailles d'écran, vous pouvez utiliser la classe DisplayMetrics pour définir les limites de manière efficace. – Khobaib

12

Ces gars ont fait un excellent travail, c'est ma solution en utilisant la bibliothèque Picasso Square:

//... 
final TextView textView = (TextView) findViewById(R.id.description); 
     Spanned spanned = Html.fromHtml(getIntent().getStringExtra(EXTRA_DESCRIPTION), 
       new Html.ImageGetter() { 
        @Override 
        public Drawable getDrawable(String source) { 
         LevelListDrawable d = new LevelListDrawable(); 
         Drawable empty = getResources().getDrawable(R.drawable.abc_btn_check_material);; 
         d.addLevel(0, 0, empty); 
         d.setBounds(0, 0, empty.getIntrinsicWidth(), empty.getIntrinsicHeight()); 
         new ImageGetterAsyncTask(DetailActivity.this, source, d).execute(textView); 

         return d; 
        } 
       }, null); 
     textView.setText(spanned); 
//... 


class ImageGetterAsyncTask extends AsyncTask<TextView, Void, Bitmap> { 


    private LevelListDrawable levelListDrawable; 
    private Context context; 
    private String source; 
    private TextView t; 

    public ImageGetterAsyncTask(Context context, String source, LevelListDrawable levelListDrawable) { 
     this.context = context; 
     this.source = source; 
     this.levelListDrawable = levelListDrawable; 
    } 

    @Override 
    protected Bitmap doInBackground(TextView... params) { 
     t = params[0]; 
     try { 
      Log.d(LOG_CAT, "Downloading the image from: " + source); 
      return Picasso.with(context).load(source).get(); 
     } catch (Exception e) { 
      return null; 
     } 
    } 

    @Override 
    protected void onPostExecute(final Bitmap bitmap) { 
     try { 
      Drawable d = new BitmapDrawable(context.getResources(), bitmap); 
      Point size = new Point(); 
      ((Activity) context).getWindowManager().getDefaultDisplay().getSize(size); 
      // Lets calculate the ratio according to the screen width in px 
      int multiplier = size.x/bitmap.getWidth(); 
      Log.d(LOG_CAT, "multiplier: " + multiplier); 
      levelListDrawable.addLevel(1, 1, d); 
      // Set bounds width and height according to the bitmap resized size 
      levelListDrawable.setBounds(0, 0, bitmap.getWidth() * multiplier, bitmap.getHeight() * multiplier); 
      levelListDrawable.setLevel(1); 
      t.setText(t.getText()); // invalidate() doesn't work correctly... 
     } catch (Exception e) { /* Like a null bitmap, etc. */ } 
    } 
} 

Mes 2 cents ... Paix!

+0

C'est génial! – aidan

+0

ouah!copier, coller, apprécier: D – Labe

+0

puis-je demander pourquoi 'LevelListDrawable' est utilisé ici @cass_? Que sont tous ces 'addLevel' et' setLevel'? – ericn