1. 程式人生 > >Android專案中遇到的坑之(Android圓角圓形圖 二)

Android專案中遇到的坑之(Android圓角圓形圖 二)

接著上一篇的問題來研究研究:
**問題來了:效果是有了,但有發現麼?我設定的scaleType只有fitxy
是有效果的,其他的都沒有效果了。設定為其他的scaleType都變成matrix那種效果了,也就是圖片預設從控制元件的左上角開始擺放。**

我們先看看ImageView的scaleType

讓我們看看scaleType的各類效果圖:
ImageView的scaleType的屬性有好幾種,分別是matrix(預設)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY

android:scaleType=”center”

保持原圖的大小,顯示在ImageView的中心。當原圖的size大於ImageView的size,超過部分裁剪處理。

android:scaleType=”centerCrop”

以填滿整個ImageView為目的,將原圖的中心對準ImageView的中心,等比例放大原圖,直到填滿ImageView為止(指的是ImageView的寬和高都要填滿),原圖超過ImageView的部分作裁剪處理。

android:scaleType=”centerInside”

以原圖完全顯示為目的,將圖片的內容完整居中顯示,通過按比例縮小原圖的size寬(高)等於或小於ImageView的寬(高)。如果原圖的size本身就小於ImageView的size,則原圖的size不作任何處理,居中顯示在ImageView。

android:scaleType=”matrix”

不改變原圖的大小,從ImageView的左上角開始繪製原圖,原圖超過ImageView的部分作裁剪處理。

android:scaleType=”fitCenter”

把原圖按比例擴大或縮小到ImageView的ImageView的高度,居中顯示

android:scaleType=”fitEnd”

把原圖按比例擴大(縮小)到ImageView的高度,顯示在ImageView的下部分位置

android:scaleType=”fitStart”

把原圖按比例擴大(縮小)到ImageView的高度,顯示在ImageView的上部分位置

android:scaleType=”fitXY”

把原圖按照指定的大小在View中顯示,拉伸顯示圖片,不保持原比例,填滿ImageView.

下面附上效果圖:

原圖為Pocoyo的頭像,上圖為原圖的size大於ImageView的size,下圖為原圖的size小於ImageView的size
這裡寫圖片描述

已經展示的很清楚了,也就是說我們的RoundImageView只支援fitxy跟martrix兩種方式,我們看看ImageView的原始碼找找原因

問題1:為什麼只支援fitxy跟matrix方式呢?
我們看看ImageView原始碼

 private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }

        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;

        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            ......
        }
   }

我們看到這麼一行程式碼:

mDrawable.setBounds(0, 0, vwidth, vheight);

當為fitxy的時候給mDrawable(也就是我們設定的那張圖片)設定了bounds為vwidth,vheight,也就是控制元件的寬高。所以fitxy時才會鋪滿整個螢幕的。

搞懂了fitxy,那麼matrix又是怎麼樣的呢?
我們看看onDraw方法:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mDrawable == null) {
            return; // couldn't resolve the URI
        }

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

            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }

            canvas.translate(mPaddingLeft, mPaddingTop);

            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

有點長,我們看到有一行程式碼:

if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }

canvas.concat(mDrawMatrix);是指給canvas做一些改變,比如縮放、平移…… 也就是說原始碼中通過我們設定的scaleType來通過演算法計算mDrawMatrix ,然後再onDraw方法中賦給了canvas,但是我們重寫了onDraw方法,也就是說mDrawMatrix 壓根就不起作用了,所以當我們在RoundImageView中執行

//最後把我們準備好的Bitmap畫在canvas上
        canvas.drawBitmap(bitmap,0,0,null);

的時候,圖片就是預設不縮放,原圖從控制元件的左上角開始擺放的。
到此,我們終於弄懂了我們遇到的問題,既然遇到了,那我們就解決下問題。

我們看看ImageView到底是怎麼縮放圖片的:

 private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }

        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;

        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            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) {
                // The bitmap fits exactly, no transform needed.
                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;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    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;
                float scale;
                float dx;
                float dy;

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

                dx = Math.round((vwidth - dwidth * scale) * 0.5f);
                dy = Math.round((vheight - dheight * scale) * 0.5f);

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);
            } else {
                // Generate the required transform.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);

                mDrawMatrix = mMatrix;
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }

就是我們上面所說的,先通過我們設定的scaleType計算mDrawMatrix,
然後再onDraw方法中賦給canvans,再貼一遍ImageView的onDraw方法。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mDrawable == null) {
            return; // couldn't resolve the URI
        }

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

            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }

            canvas.translate(mPaddingLeft, mPaddingTop);

            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

我們自己重寫了onDraw方法,也就是我們只需要像ImageView一樣,把configureBounds方法搬到我們的控制元件中就可以了,好了,我們試試:

@Override
    protected void onDraw(Canvas canvas) {
        Bitmap bitmap = mWeakReference==null?null:mWeakReference.get();
        if(bitmap==null || bitmap.isRecycled()){
            //獲取一下設定的圖片資源
            Drawable drawable=getDrawable();
            if(drawable!=null){
                //建立一個空白畫布,用來畫模板跟原圖
                bitmap=Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_8888);
                ////修改過的程式碼
                Matrix matrix=null;
                Canvas dstCanvas=new Canvas(bitmap);
                dstCanvas.save();
                if (getScaleType()==ScaleType.FIT_XY){
                    drawable.setBounds(0,0,getWidth(),getHeight());
                    matrix=null;
                }else{
                    matrix=new Matrix();
                    configureBounds(drawable,matrix);
                }
                if(matrix!=null){
                    dstCanvas.concat(matrix);
                }
                drawable.draw(dstCanvas);
                dstCanvas.restore();
                ////修改過的程式碼
                //畫模板
                if(mMaskBitmap==null||mMaskBitmap.isRecycled()){
                    mMaskBitmap=getShapeBitmap();
                }
                dstCanvas.drawBitmap(mMaskBitmap,0,0,mPaint);
                mPaint.setXfermode(null);
            }
        }
        //最後把我們準備好的Bitmap畫在canvas上
        canvas.drawBitmap(bitmap,0,0,null);
    }
private void configureBounds(Drawable drawable, Matrix matrix) {
        ScaleType mScaleType = getScaleType();
        //獲取圖片的寬高
        int dwidth = drawable.getIntrinsicWidth();
        int dheight = drawable.getIntrinsicHeight();
        int vwidth =getWidth();
        int vheight = getHeight();
        if (ScaleType.MATRIX == mScaleType) {
            /////
        } else if (ScaleType.CENTER_CROP == mScaleType) {
            float scale;
            float dx = 0, dy = 0;

            if (dwidth * vheight > vwidth * dheight) {
                scale = (float) vheight / (float) dheight;
                dx = (vwidth - dwidth * scale) * 0.5f;
            } else {
                scale = (float) vwidth / (float) dwidth;
                dy = (vheight - dheight * scale) * 0.5f;
            }

            matrix.setScale(scale, scale);
            matrix.postTranslate(Math.round(dx), Math.round(dy));
        } else if (ScaleType.CENTER_INSIDE == mScaleType) {
            float scale;
            float dx;
            float dy;

            if (dwidth <= vwidth && dheight <= vheight) {
                scale = 1.0f;
            } else {
                scale = Math.min((float) vwidth / (float) dwidth,
                        (float) vheight / (float) dheight);
            }
            dx = Math.round((vwidth - dwidth * scale) * 0.5f);
            dy = Math.round((vheight - dheight * scale) * 0.5f);
            matrix.setScale(scale, scale);
            matrix.postTranslate(dx, dy);
        } else {
            matrix.setRectToRect(new RectF(drawable.getBounds()), new RectF(0, 0, getWidth(), getHeight()), scaleTypeToScaleToFit(mScaleType));
        }
    }

直接拖的ImageView的原始碼,不要問我演算法為什麼是這樣,我也研究了蠻久,數學不好(^__^) 嘻嘻……

到這又鬱悶了,scaleTypeToScaleToFit方法沒法copy了,怎麼辦? 反射唄!說幹咱就幹。

 private Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType mScaleType) {
        Class mClass=ImageView.class;
        try {
            Method method = mClass.getDeclaredMethod("scaleTypeToScaleToFit", new Class[]{ScaleType.class});
            method.setAccessible(true);
            if(method!=null){
                Matrix.ScaleToFit fit = (Matrix.ScaleToFit) (method.invoke(null, new Object[]{mScaleType}));
                return fit;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Matrix.ScaleToFit.FILL;
    }

不懂的童鞋自己去腦補下javase的東西哈!!

到此算是寫完了,我們再次執行程式碼:
這裡寫圖片描述

終於是完美的呈現了,從來沒寫過這麼長的部落格,小夥伴默默點個贊哈,大牛勿噴!!(^__^) 嘻嘻……