解析给ImageView设置资源的五种方法

Android开发中几乎每天都要接触ImageView这个控件。这个控件可以作为加载图片的容器。ImageView给我们提供了五种设置图片资源的方法:

  • setImageBitmap(Bitmap bm)
  • setImageDrawable(Drawable drawable)
  • setImageResource(int resId)
  • setImageURI(Uri uri)
  • setImageIcon(Icon icon)

下面就通过源码来看看这5个方法的区别。

1. setImageBitmap(Bitmap bm)

setImageBitmap方法中,先把bitmap放在一个BitmapDrawable中,然后调用的setImageDrawable方法。

    public void setImageBitmap(Bitmap bm) {
        // Hacky fix to force setImageDrawable to do a full setImageDrawable
        // instead of doing an object reference comparison
        mDrawable = null;
        if (mRecycleableBitmapDrawable == null) {
            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
        } else {
            mRecycleableBitmapDrawable.setBitmap(bm);
        }
        setImageDrawable(mRecycleableBitmapDrawable);
    }
2. setImageDrawable(Drawable drawable)

setImageDrawable方法里面先调用了updateDrawable()方法,然后判断了drawable的宽高是否有改变,如果发生了改变则会调用requestLayout()方法,最后调用invalidate()方法,让新设置的drawable生效。

  • updateDrawable()
    这个方法里用于更新drawable的可见性、宽高、边界、颜色等。

  • requestLayout()
    这个方法会根据View树逐级向上传递,最后由ViewRootImpl#requestLayout方法处理该事件。ViewRootImpl#requestLayout中会调用scheduleTraversals()方法,这是一个异步方法,会调用performTraversals()方法。在performTraversals()方法中会先后调用performMeasure()和performLayout()方法。然后视条件调用performDraw()方法。

  • invalidate()
    该方法会不断向父容器请求刷新并计算需要重绘的区域,最后会传递到ViewRootImpl中触发performTraversals方法,然后调用performDraw()进行重绘。

    /**
     * Sets a drawable as the content of this ImageView.
     *
     * @param drawable the Drawable to set, or {@code null} to clear the
     *                 content
     */
    public void setImageDrawable(@Nullable Drawable drawable) {
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;

            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;

            updateDrawable(drawable);

            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            invalidate();
        }
    }
3. setImageResource(int resId)

对比setImageDrawable方法的源码,发现这个方法实现里多了一个resolveUri()方法。resolveUri()通过Context#getDrawable方法完成从resource id解析出drawable对象。并且从源码注释中我们可以知道,这个方法对Bitmap的读取以及解码都是在UI线程里面完成的,所以这里面会有潜在的问题(比如当图片很大时,就有可能阻塞UI线程)。因此官方建议我们,如果担心这个问题,那么可以考虑使用setImageBitmap方法。

    /**
     * Sets a drawable as the content of this ImageView.
     *
     * <p class="note">This does Bitmap reading and decoding on the UI
     * thread, which can cause a latency hiccup.  If that's a concern,
     * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
     * {@link #setImageBitmap(android.graphics.Bitmap)} and
     * {@link android.graphics.BitmapFactory} instead.</p>
     *
     * @param resId the resource identifier of the drawable
     *
     * @attr ref android.R.styleable#ImageView_src
     */
    public void setImageResource(@DrawableRes int resId) {
        // The resource configuration may have changed, so we should always
        // try to load the resource even if the resId hasn't changed.
        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        updateDrawable(null);
        mResource = resId;
        mUri = null;

        resolveUri();

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
4. setImageURI(Uri uri)

setImageURI和setImageResource类似,Bitmap的读取和解码都是在UI线程中,不同的是setImageUri是通过资源的Uri来获取Drawable对象。注释中提到,这个方法有一个兼容性的问题。在API<24的机器上,如果从SCHEME_CONTENT或者SCHEME_FILE的uri资源中加载图片,将无法应用正确的密度对图片进行缩放。

 /**
     * Sets the content of this ImageView to the specified Uri.
     *
     * <p class="note">This does Bitmap reading and decoding on the UI
     * thread, which can cause a latency hiccup.  If that's a concern,
     * consider using {@link #setImageDrawable(Drawable)} or
     * {@link #setImageBitmap(android.graphics.Bitmap)} and
     * {@link android.graphics.BitmapFactory} instead.</p>
     *
     * <p class="note">On devices running SDK < 24, this method will fail to
     * apply correct density scaling to images loaded from
     * {@link ContentResolver#SCHEME_CONTENT content} and
     * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
     * on devices with SDK >= 24 <strong>MUST</strong> specify the
     * {@code targetSdkVersion} in their manifest as 24 or above for density
     * scaling to be applied to images loaded from these schemes.</p>
     *
     * @param uri the Uri of an image, or {@code null} to clear the content
     */
    @android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
    public void setImageURI(@Nullable Uri uri) {
        if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
            updateDrawable(null);
            mResource = 0;
            mUri = uri;

            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;

            resolveUri();

            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            invalidate();
        }
    }
5. setImageIcon(Icon icon)

这个方法是API23中新增的方法。它会先从Icon对象中获得Drwable对象,然后调用了setImageDrawable方法。Icon是一个序列化的图形容器,里面可以包含Bitmap、压缩图片和drawable资源。Icon#loadDrawable方法也是运行在UI线程中,所以也有可能会导致UI线程阻塞。如果担心这个问题,可以用Icon#loadDrawableAsync来获取drawable对象。

    /**
     * Sets the content of this ImageView to the specified Icon.
     *
     * <p class="note">Depending on the Icon type, this may do Bitmap reading
     * and decoding on the UI thread, which can cause UI jank.  If that's a
     * concern, consider using
     * {@link Icon#loadDrawableAsync(Context, Icon.OnDrawableLoadedListener, Handler)}
     * and then {@link #setImageDrawable(android.graphics.drawable.Drawable)}
     * instead.</p>
     *
     * @param icon an Icon holding the desired image, or {@code null} to clear
     *             the content
     */
    @android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
    public void setImageIcon(@Nullable Icon icon) {
        setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
    }

总结:

  • setImageBitmap和setImageDrawable方法由于已经提供了bitmap或者drawable对象,因此不会阻塞UI线程。setImageResource、setImageURI和setImageIcon都会在UI线程中完成对Bitmap的读取和解码,因此会存在阻塞UI线程的可能。

  • setImageBitmap、setImageResource、setImageURI和setImageIcon最后都是调用setImageDrawable方法

  • setImageResource是通过Context#getDrawable(resourceId)获取drawable对象,setImageURI是通过资源的URI获取drawable对象,setImageIcon则是通过Icon#loadDrawable方法获取drawable对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值