自定义圆形头像CircleImageView的使用和源码分析

我们使用开源控件CircleImageView来实现该效果。

CircleImageView项目下载地址:

https://github.com/hdodenhof/CircleImageView

(1).CircleImageView的使用

首先我们将CircleImageView添加到gradle。

[html]view plaincopy

dependencies {  

    compile 'de.hdodenhof:circleimageview:2.1.0'  

}  

然后看一下自定义属性attrs:

[html]view plaincopy

  

  

  

  

  


属性介绍:civ_border_width:   设置边框的宽度,默认为0,即无边框。

civ_border_color:    设置边框的颜色,默认为黑色。

civ_border_overlay:设置边框是否覆盖在图片上,默认为false,即边框在图片外圈。

civ_fill_color:           设置图片的底色,默认透明。

接下来在布局文件中引入CircleImageView。

[html]view plaincopy

xmlns:circleimageview="http://schemas.android.com/apk/res-auto"  

android:id="@+id/imageview"  

android:layout_width="wrap_content"  

android:layout_height="wrap_content"  

android:src="@drawable/profile"  

circleimageview:civ_border_color="@android:color/holo_red_light"  

circleimageview:civ_border_overlay="false"  

circleimageview:civ_border_width="2dp"  

circleimageview:civ_fill_color="@android:color/holo_blue_light"/>  

注意:CircleImageView的默认ScaleType为CENTER_CROP,且只能为CENTER_CROP。

下面来详细分析一下源码:

1. 首先从项目里调用CircleImageView开始分析,我们要么在XML里面用src,要么调用CircleImageView的setImageXXX()方法设置图片。那我们的源码的运行入口在哪里呢,是从构造函数CircleImageView()开始呢还是从setImageXXX()开始?一开始就卡壳了。一开始我以为是从构造函数CircleImageView()开始跑,结果分析下来发现并不会进入setup();所以这是行不通的,那接下来就要论证是不是从setImageXXX()开始呢?我的方法是分别在两者进行System.out.println测试,看看谁先执行。测试结果会发现是从setImageXXX()开始。

那我们看下源码:

[js] view plain copy


/**

     * 以下四个函数都是

     * 复写ImageView的setImageXxx()方法 

     * 注意这个函数先于构造函数调用之前调用

     * @param bm

     */

    @Override

    public void setImageBitmap(Bitmap bm) {

        super.setImageBitmap(bm);

        mBitmap = bm;

        setup();

    }


    @Override

    public void setImageDrawable(Drawable drawable) {

        super.setImageDrawable(drawable);

        mBitmap = getBitmapFromDrawable(drawable);

        System.out.println("setImageDrawable -- setup");  

        setup();

    }


    @Override

    public void setImageResource(@DrawableRes int resId) {

        super.setImageResource(resId);

        mBitmap = getBitmapFromDrawable(getDrawable());

        setup();

    }


    @Override

    public void setImageURI(Uri uri) {

        super.setImageURI(uri);

        mBitmap = getBitmapFromDrawable(getDrawable());

        setup();

    }

看上面代码会发现,四个函数都是获取图片Bitmap,调用setup()。

接下来那我们查看setup()源码:

[js] view plain copy


//因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内

//设置mSetupPending为true然后直接返回,后面的代码并没有执行。

if (!mReady) {

    mSetupPending = true;

    return;

}

会发现第一次进入时,第一个if语句为真,进入括号体内设置mSetupPending为true然后直接返回,后面的代码并没有执行。为什么要这样设置mReady和mSetupPending呢不执行下面的代码呢,不要急,后面再解释。

2. 接下来我们会进入构造函数CircleImageView()。查看源码:

[js] view plain copy


    /**

     * 构造函数

     */

    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

        //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

        //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;

        // 获取边界的宽度

        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);

        // 获取边界的颜色

        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);

        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);

        //调用 recycle() 回收TypedArray,以便后面重用

        a.recycle();

        System.out.println("CircleImageView -- 构造函数");  

        init();

    }

分析上面代码会发现很简单,就是首先通过TypedArray获取自定义参数,再调用init()函数:

[js] view plain copy


    /**

     * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用 

     */

    private void init() {

        //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。

        super.setScaleType(SCALE_TYPE);

        mReady = true;


        if (mSetupPending) {

            setup();

            mSetupPending = false;

        }

    }

因为在前面mSetupPending为真,则执行setup(),而且这里mReady 也为true,所以会执行setup()下面的代码。为什么要这样设置mSetupPending和mReady呢?

这是因为第一次进入setImageXXX()时候,我们无法获取自定义参数值,所以setup()下面的代码无法绘制我们想要的样式而是默认样式。而获取自定义参数只能在构造函数里,这样我们就能明白大神作者的意图了,通过设置mSetupPending和mReady控制第一次执行setup函数里下面代码要在构造函数执行完毕时。再者如果用户再进行setImageXXX()设置图片的话,就直接会执行setup()下面的代码,因为这之后mReady一直为true。

3 说了这么多,我们来看看setup()下面的代码到底是啥?没错,它就是这么酷炫,很关键:

[js] view plain copy


    /**

     * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:

     * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,

     * 以及调用updateShaderMatrix()函数和 invalidate()函数;

     */

    private void setup() {

        //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内

        //设置mSetupPending为true然后直接返回,后面的代码并没有执行。

        if (!mReady) {

            mSetupPending = true;

            return;

        }

        //防止空指针异常

        if (mBitmap == null) {

            return;

        }

        // 构建渲染器,用mBitmap来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸

        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        // 设置图片画笔反锯齿

        mBitmapPaint.setAntiAlias(true);

        // 设置图片画笔渲染器

        mBitmapPaint.setShader(mBitmapShader);

        // 设置边界画笔样式

        mBorderPaint.setStyle(Paint.Style.STROKE);

        mBorderPaint.setAntiAlias(true);

        mBorderPaint.setColor(mBorderColor);    //画笔颜色

        mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度

        //这个地方是取的原图片的宽高

        mBitmapHeight = mBitmap.getHeight();

        mBitmapWidth = mBitmap.getWidth();

        // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸

        mBorderRect.set(0, 0, getWidth(), getHeight());

        //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)

        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);

        // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)

        mDrawableRect.set(mBorderRect);

        if (!mBorderOverlay) {

            //demo里始终执行

            //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)

            mDrawableRect.inset(mBorderWidth, mBorderWidth);

        }

        //这里计算的是内圆的最小半径,也即去除边界宽度的半径

        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);

        //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充

        updateShaderMatrix();

        //手动触发ondraw()函数 完成最终的绘制

        invalidate();

    }

上面代码注释我写的很详细不再一步步解释了,进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数。

这里关于半径的计算,我画图举个例子:CircleImageView的布局宽高度均为160,边界的宽度为10如图所示:

那么去除边界宽度的内圆半径为70,带边界部分的外圆半径为75。

4 根据上面,接下来进入updateShaderMatrix()函数,查看源码:

[js] view plain copy


    /**

    * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。

    * 作用:保证图片损失度最小和始终绘制图片正中央的那部分

    */

    private void updateShaderMatrix() {

        float scale;

        float dx = 0;

        float dy = 0;


        mShaderMatrix.set(null);

        // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())

        //取最小的缩放比例

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {

            //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)

            scale = mDrawableRect.height() / (float) mBitmapHeight;

            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;

        } else {

            //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)

            scale = mDrawableRect.width() / (float) mBitmapWidth;

            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;

        }

        // shaeder的变换矩阵,我们这里主要用于放大或者缩小。

        mShaderMatrix.setScale(scale, scale);

        // 平移

        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

        // 设置变换矩阵

        mBitmapShader.setLocalMatrix(mShaderMatrix);

    }

通过updateShaderMatrix函数设置BitmapShader的Matrix参数,对图片mBitmap位置用何种缩放平移形式填充。

[js] view plain copy


if判断语句里

mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight

写成

(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())

更好理解。

目的是用最小的缩放比例,使得图片的某个方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样。做到了图片损失度最小。同时scale保证Bitmap的宽或高和目标区域一致,那么高或宽就需要进行位移,使得Bitmap居中。这里写得很nice。

5 现在万事俱备,只欠ondraw()了。接着上面再setup()最后会调用invalidate()函数触发ondraw()函数完成最终的绘制。查看源码:

[js] view plain copy


    @Override

    protected void onDraw(Canvas canvas) {

        //如果图片不存在就不画

        if (getDrawable() == null) {

            return;

        }

        //绘制内圆形,参数内圆半径,图片画笔为mBitmapPaint

        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);

      //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 参数外圆半径,边界画笔为mBorderPaint

        if (mBorderWidth != 0) {

            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);

        }

    }

使用配置好的mBitmapPaint和mBorderPaint先画出绘制内圆形来以后再画边界圆形。源码还有一些自定义设置样式函数,很简单。

大功告成,是不是觉得思路比较简单,精致干练。

我总结下源码的精致之处:

流程控制的比较严谨,比如setup函数的使用

updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分

作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。

附上完整源码:

[js] view plain copy


package com.example.library;


import com.example.circleimageviewdemo_1.R;


import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Bitmap;

import android.graphics.BitmapShader;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.ColorFilter;

import android.graphics.Matrix;

import android.graphics.Paint;

import android.graphics.RectF;

import android.graphics.Shader;

import android.graphics.drawable.BitmapDrawable;

import android.graphics.drawable.ColorDrawable;

import android.graphics.drawable.Drawable;

import android.net.Uri;

import android.support.annotation.ColorRes;

import android.support.annotation.DrawableRes;

import android.util.AttributeSet;

import android.widget.ImageView;

/**

  * 流程控制的比较严谨,比如setup函数的使用

  * updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分

  * 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。

  */

public class CircleImageView extends ImageView {

    //缩放类型

    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;

    private static final int COLORDRAWABLE_DIMENSION = 2;

    // 默认边界宽度

    private static final int DEFAULT_BORDER_WIDTH = 0;

    // 默认边界颜色

    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;

    private static final boolean DEFAULT_BORDER_OVERLAY = false;


    private final RectF mDrawableRect = new RectF();

    private final RectF mBorderRect = new RectF();


    private final Matrix mShaderMatrix = new Matrix();

    //这个画笔最重要的是关联了mBitmapShader 使canvas在执行的时候可以切割原图片(mBitmapShader是关联了原图的bitmap的)

    private final Paint mBitmapPaint = new Paint();

    //这个描边,则与本身的原图bitmap没有任何关联,

    private final Paint mBorderPaint = new Paint();

    //这里定义了 圆形边缘的默认宽度和颜色

    private int mBorderColor = DEFAULT_BORDER_COLOR;

    private int mBorderWidth = DEFAULT_BORDER_WIDTH;


    private Bitmap mBitmap;

    private BitmapShader mBitmapShader; // 位图渲染

    private int mBitmapWidth;   // 位图宽度

    private int mBitmapHeight;  // 位图高度


    private float mDrawableRadius;// 图片半径

    private float mBorderRadius;// 带边框的的图片半径


    private ColorFilter mColorFilter;

    //初始false

    private boolean mReady;

    private boolean mSetupPending;

    private boolean mBorderOverlay;

    //构造函数

    public CircleImageView(Context context) {

        super(context);

        init();

    }

    //构造函数

    public CircleImageView(Context context, AttributeSet attrs) {

        this(context, attrs, 0);

    }

    /**

     * 构造函数

     */

    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

        //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

        //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;

        // 获取边界的宽度

        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);

        // 获取边界的颜色

        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);

        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);

        //调用 recycle() 回收TypedArray,以便后面重用

        a.recycle();

        System.out.println("CircleImageView -- 构造函数");  

        init();

    }

    /**

     * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用 

     */

    private void init() {

        //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。

        super.setScaleType(SCALE_TYPE);

        mReady = true;


        if (mSetupPending) {

            setup();

            mSetupPending = false;

        }

    }


    @Override

    public ScaleType getScaleType() {

        return SCALE_TYPE;

    }

    /**

          * 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性

          *

          * @param scaleType

          */

    @Override

    public void setScaleType(ScaleType scaleType) {

        if (scaleType != SCALE_TYPE) {

            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));

        }

    }


    @Override

    public void setAdjustViewBounds(boolean adjustViewBounds) {

        if (adjustViewBounds) {

            throw new IllegalArgumentException("adjustViewBounds not supported.");

        }

    }


    @Override

    protected void onDraw(Canvas canvas) {

        //如果图片不存在就不画

        if (getDrawable() == null) {

            return;

        }

        //绘制内圆形 图片 画笔为mBitmapPaint

        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);

      //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 边界画笔为mBorderPaint

        if (mBorderWidth != 0) {

            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);

        }

    }


    @Override

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);

        setup();

    }


    public int getBorderColor() {

        return mBorderColor;

    }


    public void setBorderColor(int borderColor) {

        if (borderColor == mBorderColor) {

            return;

        }


        mBorderColor = borderColor;

        mBorderPaint.setColor(mBorderColor);

        invalidate();

    }


    public void setBorderColorResource(@ColorRes int borderColorRes) {

        setBorderColor(getContext().getResources().getColor(borderColorRes));

    }


    public int getBorderWidth() {

        return mBorderWidth;

    }


    public void setBorderWidth(int borderWidth) {

        if (borderWidth == mBorderWidth) {

            return;

        }


        mBorderWidth = borderWidth;

        setup();

    }


    public boolean isBorderOverlay() {

        return mBorderOverlay;

    }


    public void setBorderOverlay(boolean borderOverlay) {

        if (borderOverlay == mBorderOverlay) {

            return;

        }


        mBorderOverlay = borderOverlay;

        setup();

    }


    /**

     * 以下四个函数都是

     * 复写ImageView的setImageXxx()方法 

     * 注意这个函数先于构造函数调用之前调用

     * @param bm

     */

    @Override

    public void setImageBitmap(Bitmap bm) {

        super.setImageBitmap(bm);

        mBitmap = bm;

        setup();

    }


    @Override

    public void setImageDrawable(Drawable drawable) {

        super.setImageDrawable(drawable);

        mBitmap = getBitmapFromDrawable(drawable);

        System.out.println("setImageDrawable -- setup");  

        setup();

    }


    @Override

    public void setImageResource(@DrawableRes int resId) {

        super.setImageResource(resId);

        mBitmap = getBitmapFromDrawable(getDrawable());

        setup();

    }


    @Override

    public void setImageURI(Uri uri) {

        super.setImageURI(uri);

        mBitmap = getBitmapFromDrawable(getDrawable());

        setup();

    }


    @Override

    public void setColorFilter(ColorFilter cf) {

        if (cf == mColorFilter) {

            return;

        }


        mColorFilter = cf;

        mBitmapPaint.setColorFilter(mColorFilter);

        invalidate();

    }

    /**

     * Drawable转Bitmap

     * @param drawable

     * @return

     */

    private Bitmap getBitmapFromDrawable(Drawable drawable) {

        if (drawable == null) {

            return null;

        }


        if (drawable instanceof BitmapDrawable) {

             //通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap

            return ((BitmapDrawable) drawable).getBitmap();

        }


        try {

            Bitmap bitmap;


            if (drawable instanceof ColorDrawable) {

                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);

            } else {

                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);

            }


            Canvas canvas = new Canvas(bitmap);

            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());

            drawable.draw(canvas);

            return bitmap;

        } catch (OutOfMemoryError e) {

            return null;

        }

    }

    /**

     * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:

     * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,

     * 以及调用updateShaderMatrix()函数和 invalidate()函数;

     */

    private void setup() {

        //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内

        //设置mSetupPending为true然后直接返回,后面的代码并没有执行。

        if (!mReady) {

            mSetupPending = true;

            return;

        }

        //防止空指针异常

        if (mBitmap == null) {

            return;

        }

        // 构建渲染器,用mBitmap位图来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸

        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        // 设置图片画笔反锯齿

        mBitmapPaint.setAntiAlias(true);

        // 设置图片画笔渲染器

        mBitmapPaint.setShader(mBitmapShader);

        // 设置边界画笔样式

        mBorderPaint.setStyle(Paint.Style.STROKE);//设画笔为空心

        mBorderPaint.setAntiAlias(true);

        mBorderPaint.setColor(mBorderColor);    //画笔颜色

        mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度

        //这个地方是取的原图片的宽高

        mBitmapHeight = mBitmap.getHeight();

        mBitmapWidth = mBitmap.getWidth();

        // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸

        mBorderRect.set(0, 0, getWidth(), getHeight());

        //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)

        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);

        // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)

        mDrawableRect.set(mBorderRect);

        if (!mBorderOverlay) {

            //demo里始终执行

            //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)

            mDrawableRect.inset(mBorderWidth, mBorderWidth);

        }

        //这里计算的是内圆的最小半径,也即去除边界宽度的半径

        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);

        //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充

        updateShaderMatrix();

        //手动触发ondraw()函数 完成最终的绘制

        invalidate();

    }

    /**

    * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。

    * 作用:保证图片损失度最小和始终绘制图片正中央的那部分

    */

    private void updateShaderMatrix() {

        float scale;

        float dx = 0;

        float dy = 0;


        mShaderMatrix.set(null);

        // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())

        //取最小的缩放比例

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {

            //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)

            scale = mDrawableRect.height() / (float) mBitmapHeight;

            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;

        } else {

            //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)

            scale = mDrawableRect.width() / (float) mBitmapWidth;

            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;

        }

        // shaeder的变换矩阵,我们这里主要用于放大或者缩小。

        mShaderMatrix.setScale(scale, scale);

        // 平移

        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

        // 设置变换矩阵

        mBitmapShader.setLocalMatrix(mShaderMatrix);

    }


}

Android自定义View总结

步骤

1. 自定义View的属性

2. 继承View,重写构造函数,获取我们自定义的属性值

3. 重写onMesure()方法

4. 重写onDraw()方法

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容