16

I'm developing an App which draws lines over a bunch of Images. To choose these images, I have a radio group and, whenever the user clicks in a radio button, the image is load with all its own drawings.

In my radio listenner I have the following code:

bitmap = BitmapUtils.decodeSampledBitmapFromResource(root + DefinesAndroid.CAMINHO_SHOPPINGS_SDCARD + nomeImagemAtual, size.x, size.y);
mImage.setImageBitmap(bitmap);

mImage.setDrawLines(true);
mImage.setImageBitmap(loadBitmapFromView(mImage));

the decodeSampledBitmapFromResource method I got from this link on android developers (it loads bitmaps more effitiently) http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

And here's the method I call to get a Bitmap of a View

    public static Bitmap loadBitmapFromView(View v) {
        Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);                
        Canvas c = new Canvas(b);
        v.layout(0, 0, v.getLayoutParams().width, v.getLayoutParams().height);
        v.draw(c);
        return b;
}

I'm setting the image Bitmap of mImage because I'm using ImageViewTouch library (which enables pinch zooming over an ImageView) and if I don't do it, all the canvas drawing is deleted with any interaction over the image (like zooming in/out).

The error log is the following

07-11 21:13:41.567: E/AndroidRuntime(20056): java.lang.IllegalArgumentException: width and height must be > 0
07-11 21:13:41.567: E/AndroidRuntime(20056):    at android.graphics.Bitmap.createBitmap(Bitmap.java:638)
07-11 21:13:41.567: E/AndroidRuntime(20056):    at android.graphics.Bitmap.createBitmap(Bitmap.java:620)

I'm almost sure that this error is occuring cause the image bitmap is not completely loaded when I call getBitmapFromView method.

How can I know when the view is loaded completely?

Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
Felipe Mosso
  • 3,907
  • 11
  • 38
  • 61
  • uhm I don't get it would you explain for me again in your code ? you've setImageBitmap the first time. Why you want to set it the second time and load with and height from view ? – Van Vu Jul 12 '13 at 03:06
  • the first time is to load the image clean (without any drawings). then I draw the lines over the images by calling setDrawLines.. once the drawings are drawn on Canvas, I have to create one unique image of the image + drawings.. so I call, for the second time, the imageBitmap, but this time, I have to get the bitmap from the view (cause the view has the drawings).. this way I'm putting them together – Felipe Mosso Jul 12 '13 at 03:13
  • It seems that this http://stackoverflow.com/questions/8831642/how-to-draw-lines-over-imageview-on-android would be more of what you want. – Van Vu Jul 12 '13 at 03:21
  • this question asks about drawing lines over image.. I'm able to drawn them normally, the problem is changing bitmaps and drawing the lines right after.. the bitmap seems to be not loading at time – Felipe Mosso Jul 12 '13 at 03:25
  • It seems that you've override the ImageView right? How about add triggering at onDraw to known when it was draw. Good question I'm eager to know the answer as well – Van Vu Jul 12 '13 at 03:40
  • yeap.. I have an ImageViewTouch that extends ImageView.. I might be doing something very stupid.. i have been on this question for hours – Felipe Mosso Jul 12 '13 at 03:46

3 Answers3

29

Call loadBitmapFromView in a such way:

mImage.post(new Runnable() {
    @Override
    public void run() {
        loadBitmapFromView(mImage);
    }
});

Runnable provided to post() method will be executed after view measuring and layouting, so getWidth() and getHeight() will return actual width and height.

What else can you do, is measuring View manually, by invoking measure, and then taking result from getMeasuredWidth() and getMeasuredHeight(). But I do not recommend this way.

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
17

There is actually another, more reliable way to do that by using ViewTreeObserver.OnPreDrawListener. And here is an example:

mImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        try {
            loadBitmapFromView(mImage);            
             // Note that returning "true" is important,
             // since you don't want the drawing pass to be canceled
            return true;    
        } finally {
            // Remove listener as further notifications are not needed
            mImage.getViewTreeObserver().removeOnPreDrawListener(this);
        }
    }
});

Using OnPreDrawListener guarantees that View was measured and layouted, while View#post(Runnable) just executes your Runnable when all Views are already most likely measured and layouted.

Derek Lee
  • 3,452
  • 3
  • 30
  • 39
Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
  • +1 for using preDraw, for mentioning that returning true is important, and for cleaning up the listener in a nice try ... finally block! – Derek Lee Nov 12 '20 at 07:01
1

Below is a working NotifyImageView class that incorporates Dmitry's method above and adds code to notify only after the ImageView is truly usable.

I hope you find it useful.

`

public interface NotifyImageHolder {
    public void notifyImageChanged(final NotifyImageView thePosterImage, final int width, final int height);
}

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

public class NotifyImageView extends ImageView {
    private boolean mImageChanged;
    private NotifyImageHolder mHolder;
    private boolean mImageFinished;

    public NotifyImageView(Context context) {
        super(context);
        init();
    }

    public NotifyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public NotifyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    protected void init() {
        mImageChanged = false;
        mImageFinished = false;
        mHolder = null;
        monitorPreDraw();
    }

    // so we can tell when the image finishes loading..
    protected void monitorPreDraw() {
        final NotifyImageView thePosterImage = this;
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {
                try {
                    return true; //note, that "true" is important, since you don't want drawing pass to be canceled
                } finally {
                    getViewTreeObserver().removeOnPreDrawListener(this); // we don't need any further notifications
                    thePosterImage.buildDrawingCache();
                    mImageFinished = true;
                }
            }
        });
    }

    public void setNotifyImageHolder(NotifyImageHolder holder) {
        this.mHolder = holder;
    }

    public boolean isImageChanged() {
        return mImageChanged;
    }

    public boolean isImageFinished() {
        return mImageFinished;
    }

    public void notifyOff() {
        mHolder = null;
    }

    // the change notify happens here..
    @Override
    public void setImageDrawable(Drawable noPosterImage) {
        super.setImageDrawable(noPosterImage);
        if (mHolder != null && mImageFinished) {
            mImageFinished = false; // we send a single change-notification only
            final NotifyImageView theNotifyImageView = this;

            theNotifyImageView.post(new Runnable() {
                @Override
                public void run() {
                    if (mHolder != null) {
                        int width = getMeasuredWidth();
                        int height = getMeasuredHeight();
                        mImageChanged = true;
                        mHolder.notifyImageChanged(theNotifyImageView, width, height);
                    }
                }
            });

        }
    }

}

`

Lee Hounshell
  • 842
  • 9
  • 10
  • 1
    You don't actually need to use view tree observer here. Since you have full access to the view implementation you can just get your size in `onDraw()` method. – Dmitry Zaytsev Oct 24 '15 at 10:05