如何在Android中获取ImageView完全加载的时间?

16

我正在开发一款应用程序,它可以在一堆图像上绘制线条。为了选择这些图像,我使用了一个单选按钮组,每当用户点击单选按钮时,相应的图像就会加载并呈现所有已有的绘图。

在我的单选按钮监听器中,我有以下代码:

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

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

我从安卓开发者网站上找到了decodeSampledBitmapFromResource方法(它可以更高效地加载位图)的链接:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

以下是我调用以获得视图位图的方法:

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

我正在设置mImage的位图,因为我正在使用ImageViewTouch库(它允许在ImageView上进行捏合缩放),如果我不这样做,所有与图像交互(如缩放)的画布绘制都将被删除。

错误日志如下:

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)

我几乎可以确定这个错误是因为当我调用getBitmapFromView方法时,图像位图还没有完全加载。

我怎样才能知道视图什么时候完全加载?


嗯,我不明白。你能再用代码解释一遍吗? 你第一次已经使用了setImageBitmap。为什么你还想第二次设置它,并且从视图中加载宽度和高度? - Van Vu
首先加载干净的图像(没有任何绘画)。然后通过调用setDrawLines在图像上绘制线条。一旦在画布上完成绘画,我必须创建一个包含图像和绘画的唯一图像。因此,第二次调用imageBitmap时,我必须从视图中获取位图(因为视图具有绘画)。这样就可以将它们组合在一起了。 - Felipe Mosso
看起来这个 https://dev59.com/1l_Va4cB1Zd3GeqPRDE0 更符合你的需求。 - Van Vu
这个问题涉及在图像上绘制线条。我能够正常地画出它们,但问题是在更改位图并立即绘制线条时,位图似乎无法及时加载。 - Felipe Mosso
看起来你已经重写了ImageView,对吧?那么在onDraw中添加触发器以知道何时绘制它如何?好问题,我也很想知道答案。 - Van Vu
是的,我有一个扩展ImageView的ImageViewTouch.. 我可能在做一些非常愚蠢的事情.. 我已经在这个问题上花了几个小时了。 - Felipe Mosso
3个回答

29

以这样的方式调用loadBitmapFromView

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

post()方法提供的Runnable将在视图测量和布局之后执行,因此getWidth()getHeight()将返回实际宽度和高度。

另外,您还可以通过调用measure手动测量View,然后从getMeasuredWidth()getMeasuredHeight()中获取结果。但我不建议这种方式。


终于成功了!我刚刚在run()方法中设置了setImageBitmap,一切都很顺利!非常感谢!如果可以的话,我会给你的答案投10次票的! :) - Felipe Mosso
同意Felipe的观点...我花了很长时间搜索,试图找到类似的方法。谢谢! - RiddlerDev
2
@RiddlerDev,请查看我发布的使用OnPreDrawListener的另一种解决方案。 - Dmitry Zaytsev
这真是救命之恩啊!Glide监听器在RecyclerView中的表现很差。 - Eduard Unruh

17

实际上,还有一种更可靠的方法可以使用 ViewTreeObserver.OnPreDrawListener 来完成。以下是一个示例:

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

OnPreDrawListener的使用确保View已被测量和布局,而View#post(Runnable)只是在所有视图很可能已被测量和布局后执行您的Runnable


使用preDraw加1分,提到返回true的重要性加1分,并在一个try...finally块中清理监听器可以获得额外加分! - Derek Lee

1
以下是一个可工作的NotifyImageView类,它包含了Dmitry上面的方法,并添加了代码以在ImageView真正可用后通知。希望您会发现它有用。

`

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

        }
    }

}

`


1
你实际上不需要在这里使用视图树观察器。由于您完全可以访问视图实现,因此可以在onDraw()方法中获取大小。 - Dmitry Zaytsev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接