美文网首页基础知识
ImageView backgroud 和src的区别

ImageView backgroud 和src的区别

作者: leilifengxingmw | 来源:发表于2019-04-11 22:55 被阅读123次

    我们在布局文件中使用ImageView的时候,通常会有两种方法显示图片,设置background属性或者设置src属性。这两者有什么区别和联系呢?下面分析。

    ImageView的源码版本:9.0

    我们看个例子

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingLeft="40dp"
        tools:context=".activity.ImageViewSrcBackgroundActivity">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:text="原图"
            android:textColor="#000000"
            android:textSize="16sp" />
    
        <ImageView
            android:id="@+id/ivOriginal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/balloon" />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:text="设置src属性,设置background属性为灰色,ImageView宽高为200dp*200dp"
            android:textColor="#000000"
            android:textSize="16sp" />
    
        <ImageView
            android:id="@+id/ivSrc"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#FDA1A1A3"
            android:src="@drawable/balloon" />
    
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginTop="40dp"
            android:gravity="center_vertical"
            android:text="设置background属性,ImageView宽高为200dp*200dp"
            android:textColor="#000000"
            android:textSize="16sp" />
    
        <ImageView
            android:id="@+id/ivBackground"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@drawable/balloon" />
    
    
    </LinearLayout>
    

    运行结果


    Screenshot_1554942750.png

    先说下结论

    1. 图片的缩放类型会影响src,不会影响background。
    2. background总会充满整个ImageView的大小(当然去掉padding)。当设置background是一张图片的时候可能会导致图片会拉伸(除非图片宽高比和ImageView的宽高比一样)。
    3. src和background可以同时存在,src会覆盖在background上面。

    下面进行分析。

    我们先看一下ImageView的构造函数精简版

    public ImageView(Context context, AttributeSet attrs, int defStyleAttr,
                int defStyleRes) {
            //调用父类的方法
            super(context, attrs, defStyleAttr, defStyleRes);
            //...
            final TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
            //获取src属性
            final Drawable d = a.getDrawable(R.styleable.ImageView_src);
            if (d != null) {
                //调用setImageDrawable方法
                setImageDrawable(d);
            }
            //...
            a.recycle();
        }
    

    ImageView的setImageDrawable方法

    /**
     * 设置一个drawable作为ImageView的内容
     *
     * @param drawable 要被设置的Drawable对象,如果为null的话则清除ImageView的内容
     */
    public void setImageDrawable(Drawable drawable) {
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;
    
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            //注释1处
            updateDrawable(drawable);
            //注释2处
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            //注释3处
            invalidate();
        }
    }
    

    上面方法的注释1处调用了updateDrawable方法。

    private void updateDrawable(Drawable d) {
            
            //...
            boolean sameDrawable = false;
    
            if (mDrawable != null) {
                sameDrawable = mDrawable == d;
                mDrawable.setCallback(null);
                unscheduleDrawable(mDrawable);
                if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                    mDrawable.setVisible(false, false);
                }
            }
    
            mDrawable = d;
    
            if (d != null) {
                d.setCallback(this);
                //...
                //获取drawable的宽高
                mDrawableWidth = d.getIntrinsicWidth();
                mDrawableHeight = d.getIntrinsicHeight();
                applyImageTint();
                applyColorMod();
                //注释1处
                configureBounds();
            } else {
                mDrawableWidth = mDrawableHeight = -1;
            }
        }
    

    在上面方法的注释1处,调用了configureBounds方法,这个方法就是用来确定drawable的绘制区域。

    private fun configureBounds() {
        //...
        //drawable想要的宽高
        val dwidth = mDrawableWidth
        val dheight = mDrawableHeight
    
        //去掉所有的padding,就是ImageView可以绘制的宽高范围
        val vwidth = getWidth() - mPaddingLeft - mPaddingRight
        val vheight = getHeight() - mPaddingTop - mPaddingBottom
    
        val fits = (dwidth < 0 || vwidth == dwidth) && (dheight < 0 || vheight == dheight)
    
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* 如果drawable没有固有的尺寸,或者ImageView的缩放类型是ScaleType.FIT_XY,
             * 则让drawable绘制区域占满ImageView可绘制的宽高范围。
             */
            mDrawable.setBounds(0, 0, vwidth, vheight)
            mDrawMatrix = null
        } else {
            // 我们需要自己处理缩放,所以我们让drawable使用固有的宽高。
            mDrawable.setBounds(0, 0, dwidth, dheight)
    
            if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null
                } else {
                    mDrawMatrix = mMatrix
                }
            } else if (fits) {
                // drawable的宽高和ImageView可绘制的宽高相等,不需要转换。
                mDrawMatrix = null
            } else if (ScaleType.CENTER == mScaleType) {
                // Center bitmap in view, no scaling.
                mDrawMatrix = mMatrix
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                        Math.round((vheight - dheight) * 0.5f))
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix
    
                val scale: Float
                var dx = 0f
                var dy = 0f
    
                if (dwidth * vheight > vwidth * dheight) {
                    scale = vheight.toFloat() / dheight.toFloat()
                    dx = (vwidth - dwidth * scale) * 0.5f
                } else {
                    scale = vwidth.toFloat() / dwidth.toFloat()
                    dy = (vheight - dheight * scale) * 0.5f
                }
    
                mDrawMatrix.setScale(scale, scale)
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy))
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
                mDrawMatrix = mMatrix
                val scale: Float
                val dx: Float
                val dy: Float
    
                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f
                } else {
                    scale = Math.min(vwidth.toFloat() / dwidth.toFloat(),
                            vheight.toFloat() / dheight.toFloat())
                }
    
                dx = Math.round((vwidth - dwidth * scale) * 0.5f).toFloat()
                dy = Math.round((vheight - dheight * scale) * 0.5f).toFloat()
    
                mDrawMatrix.setScale(scale, scale)
                mDrawMatrix.postTranslate(dx, dy)
            } else {
                //ImageView的默认缩放类型是ScaleType.FIT_CENTER,所以会走到这里
                // 生成必要的转换
                //drawable的绘制区域大小
                mTempSrc.set(0, 0, dwidth, dheight)
                //ImageView可绘制区域的大小
                mTempDst.set(0, 0, vwidth, vheight)
                mDrawMatrix = mMatrix
                //根据缩放类型,最终确定drawable的绘制区域大小
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType))
            }
        }
    }
    

    在configureBounds方法方法中我们也注意到了,图片的缩放类型会影响src。

    我们回到ImageView的setImageDrawable方法的注释2处,和注释3处。

    //注释2处
    if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
        requestLayout();
    }
    //注释3处
    invalidate();
    

    这最终会导致View重绘。我们看下View的draw方法的精简版

    public void draw(Canvas canvas) {
            //...
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
            
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
            //...
            if (!verticalEdges && !horizontalEdges) {
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                // Step 4, draw the children
                dispatchDraw(canvas);
    
                drawAutofilledHighlight(canvas);
    
                //...
                // Step 6, draw decorations (foreground, scrollbars)
                onDrawForeground(canvas);
    
                // Step 7, draw the default focus highlight
                drawDefaultFocusHighlight(canvas);
    
                if (debugDraw()) {
                    debugDrawFocus(canvas);
                }
    
                // we're done...
                return;
            }
    
        //...
    }
    

    第1步是调用drawBackground(Canvas canvas) 方法

    private void drawBackground(Canvas canvas) {
        //注释1处
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        //注释2处
        setBackgroundBounds();
    
        // ...
        //是否要移动画布
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            //绘制背景
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
    

    在注释1处,首先将mBackground赋值给background。我们看下View的构造函数。

    public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);
    
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        //...
        Drawable background = null;
        //...
        
        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.View_background:
                    //获取background
                    background = a.getDrawable(attr);
                    break;
                //...
        }
     }
    //...
      if (background != null) {
          //注释1处
          setBackground(background);
      }
    }
    

    在构造函数的注释1处,调用了setBackground方法。

    public void setBackground(Drawable background) {
            //noinspection deprecation
            setBackgroundDrawable(background);
        }
    
     public void setBackgroundDrawable(Drawable background) {
            computeOpaqueFlags();
    
            if (background == mBackground) {
                return;
            }
    
            boolean requestLayout = false;
    
            mBackgroundResource = 0;
    
            /*
             * Regardless of whether we're setting a new background or not, we want
             * to clear the previous drawable. setVisible first while we still have the callback set.
             */
            if (mBackground != null) {
                if (isAttachedToWindow()) {
                    mBackground.setVisible(false, false);
                }
                mBackground.setCallback(null);
                unscheduleDrawable(mBackground);
            }
    
            if (background != null) {
                Rect padding = sThreadLocal.get();
                if (padding == null) {
                    padding = new Rect();
                    sThreadLocal.set(padding);
                }
             
               
                // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
                // if it has a different minimum size, we should layout again
                if (mBackground == null
                        || mBackground.getMinimumHeight() != background.getMinimumHeight()
                        || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                    requestLayout = true;
                }
    
                // Set mBackground before we set this as the callback and start making other
                // background drawable state change calls. In particular, the setVisible call below
                // can result in drawables attempting to start animations or otherwise invalidate,
                // which requires the view set as the callback (us) to recognize the drawable as
                // belonging to it as per verifyDrawable.
                //为mBackground赋值
                mBackground = background;
                if (background.isStateful()) {
                    background.setState(getDrawableState());
                }
                if (isAttachedToWindow()) {
                    background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
                }
    
                applyBackgroundTint();
    
                // Set callback last, since the view may still be initializing.
                background.setCallback(this);
    
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                    requestLayout = true;
                }
            } else {
                //传入的background为null
                /* Remove the background */
                //mBackground赋值为null
                mBackground = null;
                //...
    
                /*
                 * When the background is set, we try to apply its padding to this
                 * View. When the background is removed, we don't touch this View's
                 * padding. This is noted in the Javadocs. Hence, we don't need to
                 * requestLayout(), the invalidate() below is sufficient.
                 */
    
                // The old background's minimum size could have affected this
                // View's layout, so let's requestLayout
                requestLayout = true;
            }
    
            if (requestLayout) {
                requestLayout();
            }
    
            mBackgroundSizeChanged = true;
            //请求重新绘制
            invalidate(true);
            invalidateOutline();
        }
    

    现在我们找到了mBackground,我们回到drawBackground(Canvas canvas)方法的注释2处。

    我们注意下,mBackground设置的绘制区域就是整个ImageView的大小(去掉padding),也就是说mBackground会充满整个ImageView,这就是为什么我们设置一个图片作为背景的时候,图片会被拉伸的原因。

    void setBackgroundBounds() {
            if (mBackgroundSizeChanged && mBackground != null) {
                mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
                mBackgroundSizeChanged = false;
                rebuildOutline();
            }
        }
    

    现在drawBackground(Canvas canvas)方法完了,我们回到draw(Canvas canvas)方法的第3步。调用onDraw(canvas)方法。ImageView重写了这个方法,我们直接看ImageView的onDraw(canvas)方法。

     @Override
     protected void onDraw(Canvas canvas) {
        //父类是空实现
        super.onDraw(canvas);
        //mDrawable为null则返回
        if (mDrawable == null) {
            return;
        }
        //没有绘制区域,返回
        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
            return;     // nothing to draw (empty bounds)
        }
    
        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            mDrawable.draw(canvas);
        } else {
            final int saveCount = canvas.getSaveCount();
            canvas.save();
    
            //...
    
            canvas.translate(mPaddingLeft, mPaddingTop);
    
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            //绘制
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }
    

    结论再说一下。

    1. 图片的缩放类型会影响src,不会影响background。
    2. background总会充满整个ImageView的大小(当然去掉padding)。当设置为background是一张图片的时候可能会导致图片会拉伸(除非图片宽高比和ImageView的宽高比一样)。
    3. src和background可以同时存在,src会覆盖在background上面。

    P.S. 关于ImageView的缩放属性可以参考如下链接:

    1. Android ImageView 的scaleType属性详解(一)
    2. Android ImageView 的 scaleType属性详解(二)

    相关文章

      网友评论

        本文标题:ImageView backgroud 和src的区别

        本文链接:https://www.haomeiwen.com/subject/dpwwiqtx.html