CircleImageView用法及源码解析(雷惊风)

   首先这篇文章我们从以下四个方面进行一一讲解1.CircleImageViewAS中集成及用法;2.CircleImageView中定义的对外的方法;3.源码解析;4.用到的知识点的总计。希望通过本篇文章的学习,您会对自定义控件及自定义控件中用到的一些类有一定的了解,最后我会把我添加好详细注释的Demo下载地址附上,你也可以一边看我的Demo,一边看这篇文章,效果应该会更好,那么,我们这就开始吧!

1.CircleImageViewAS中集成及用法

   我们想在AS下使用CircleImageView只需要在我们app下的build.gradle中添加一行配置即可,很简单,如下最后一行:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.0.0'
    compile 'de.hdodenhof:circleimageview:2.1.0'
}

配置完了以后,我们就可以在我们的xml布局文件中引入或者.java文件中创建我们的CircleImageView并且使用了,到目前为止它的最新版本为2.1.0,它在github上的下载地址如下:https://github.com/hdodenhof/CircleImageView 好吧,集成就讲这么点,很简单。

   下面我们讲一下CircleImageView的具体用法,在将具体用法之前,我们先新建一个最新的android项目按照上述集成方法,集成我们的CircleImageView,集成完成后我们就可以在我们的XML布局文件中引入我们的控件了,如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/circleImageView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@mipmap/psb"
        app:civ_border_color="@android:color/holo_red_dark"
        app:civ_border_width="5dp" />
</RelativeLayout>

然后将XML文件与Acitivity进行绑定,运行项目就会看到如下结果:


   我们还可以在代码中对CircleImageView进行一些设置如禁用图片圆形属性等,这些在下一节CircleImageView中定义的对外的方法中讲解。

2.CircleImageView中定义的对外的方法

我们打开CircleImageView.java文件查看他的public方法,下边我一一说明:

@Override
public ScaleType getScaleType() {
    return SCALE_TYPE;
}

解释:外部类获取CircleImageView的ScaleType属性。

@Override
public void setScaleType(ScaleType scaleType) {
    if (scaleType != SCALE_TYPE) {
        throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
    }
}

解释:重写父类方法,,CircleImageView设置ScaleType属性,这里需要注意如果设置的scaleType不是ScaleType.CENTER_CROP,抛出异常,只支持ScaleType.CENTER_CROP;

@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
    if (adjustViewBounds) {
        throw new IllegalArgumentException("adjustViewBounds not supported.");
    }
}

解释:重写父类方法,adjustViewBounds属性为是否保持宽高比。需要与maxWidth、MaxHeight一起使用,否则单独使用没有效果。当前控件不支持设置保持宽高比;起到禁止设置的作用。

@Override
public void setPadding(int left, int top, int right, int bottom) {
    super.setPadding(left, top, right, bottom);
    setup();
}

解释:设置padding属性,该控件兼容设置padding属性。

@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
    super.setPaddingRelative(start, top, end, bottom);
    setup();
}

解释:setPadding的话不管方向如何都按照左上右下的顺序来配置Padding,setPaddingRelative的话则会按照配置的LayoutDirection来进行设置从左到右的话为左上右下,从右到左的话为右上左下的顺序Android4.0以后添加)。

public int getBorderColor() {
    return mBorderColor;
}

解释:获取外边框圆环颜色。

public void setBorderColor(@ColorInt int borderColor) {
    if (borderColor == mBorderColor) {
        return;
    }
    mBorderColor = borderColor;
    mBorderPaint.setColor(mBorderColor);
    invalidate();
}

解释:设置外边框圆环颜色。

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

解释:设置外边圆环是否压住内部圆形图片。

public boolean isDisableCircularTransformation() {
    return mDisableCircularTransformation;
}

解释:是否禁用图片圆形属性。如果为true,则就是普通方形图片。

public void setDisableCircularTransformation(boolean disableCircularTransformation) {
    if (mDisableCircularTransformation == disableCircularTransformation) {
        return;
    }
    mDisableCircularTransformation = disableCircularTransformation;
    initializeBitmap();
}

解释:设置是否禁用图片圆形属性。

@Override
public void setImageBitmap(Bitmap bm) {
    super.setImageBitmap(bm);
    initializeBitmap();
}
@Override
public void setImageDrawable(Drawable drawable) {
    super.setImageDrawable(drawable);
    System.out.println("Log_setImageDrawable()");
    initializeBitmap();
}
@Override
public void setImageResource(@DrawableRes int resId) {
    super.setImageResource(resId);
    System.out.println("Log_setImageResource()");
    initializeBitmap();
}
@Override
public void setImageURI(Uri uri) {
    super.setImageURI(uri);
    initializeBitmap();
}

解释:四种重写父类设置图片方法。

PS:如果我们在XML中设置了android:src属性,会执行我们的第一个方法(setImageBitmap)该方法会先于构造函数调用之前调用。后边在源码讲解中详细说明。

@Override
public void setColorFilter(ColorFilter cf) {
    if (cf == mColorFilter) {
        return;
    }
    mColorFilter = cf;
    applyColorFilter();
    invalidate();
}

解释:重写父类方法,设置ColorFilter,查看ColorFilter文档你会发现,ColorFilter有三个子类:ColorMatrixColorFilter:颜色矩阵过滤器;LightingColorFilter:“光照色彩过滤器”,模拟一个光照照过图像所产生的效果;PorterDuffColorFilter:PorterDuff混合模式的色彩过滤器。如果你想了解相关知识可以查相关文档,这里就不详细讲了,超出本文范围。

@Override
public ColorFilter getColorFilter() {
    return mColorFilter;
}

解释:获取着色器。

   到目前位置,整个CircleImageView中的建议使用的公共方法差不多就上述这么些,还有一些现在已经不建议使用了,我就没有拿出来,比如设置图片背景颜色啊等等,已经用注解@Deprecated进行了标注,如下:

@Deprecated
public void setFillColor(@ColorInt int fillColor) {
    if (fillColor == mFillColor) {
        return;
    }
    System.out.println("Log_setFillColor()");
    mFillColor = fillColor;
    mFillPaint.setColor(fillColor);
    invalidate();
}

我们CircleImageView的所有public方法都进行了说明,那我们的控件您肯定就会用了,再不会用,我相信你已经没救了,赶紧骑上大母猪飞奔吧!

3.源码解析

   这个小结我们开始进入本篇的重点,就是了解它是如何实现的,在我们讲解以前,为了添加注释方便,我们先在我们的项目下新建一个名称一模一样的.java文件,将原CircleImageView文件中代码复制一份,粘进去,可以看到我们的控件是继承了ImageView的,在ImageView的基础上进行扩展。这样就可以了。因为我们之前已经在app下的build.gradle中引入过CircleImageView了,所以不用去拷贝如下代码:

<resources>
    <declare-styleable name="CircleImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
    </declare-styleable>
</resources>

如果您没有配置过build.gradle,就需要复制了,否则会报错。下面我们将XML中的引用改成我们自己刚建的CircleImageview运行,结果依然可以显示,没有任何区别。好了下面我们进入主题吧!

打开我们的CircleimageView你会发现,它也有三个构造函数,如下:

public CircleImageView(Context context) {
    super(context);
    System.out.println("Log_单参构造");
    init();
}

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);

}

public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    System.out.println("Log_多参构造");
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

    mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
    mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
    mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
    mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
    a.recycle();
    init();
}

第一个构造函数是在代码中new对象的时候执行,第二个是在XML中引用的时候调用,这里跟我们一般定义控件没什么区别。我们接下来就寻找程序入口,看他是如何运行的,按照我们一般使用View来说,首先看一种情况,在XML里边引用,并且不设置android:src属性,我们知道,在XML里边引用程序会走我们的第二个构造函数,好吧,我们看一下第二个构造函数:

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

可以看到,在我们的第二个构造函数中调用了我们第三个构造函数,三参构造函数如下:

public CircleImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
可以看到,在我们的第二个构造函数中调用了我们第三个构造函数,三参构造函数如下:
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    System.out.println("Log_多参构造");
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
    mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
    mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
    mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
    mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
    a.recycle();
    init();
}

这里很简单,就是通过TypedArray获取我们在XML中设置的参数值并赋值给相应参数,外圆环宽度、外圆环颜色、圆环是否压住图片、图片背景。然后调用了init()方法,下边看一下init()方法:

private void init() {
    super.setScaleType(SCALE_TYPE);
    mReady = true;

    if (mSetupPending) {
        setup();
        mSetupPending = false;
    }
}

可以看见在这里调用了父类的setScaleType()方法传入了一个SCALE_TYPE变量,这是什么东西呢?看一下它的定义,

private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

可以看到它是final的,是不可以修改的,在本篇的第二小结设置ScaleType()属性时,也可以看到,我们继承ImageView后的CircleImageView只支持CENTER_CROP这一种设置,

关于ImageView.ScaleType()相关知识可以查看如下文章:

http://blog.csdn.net/buaaroid/article/details/49360779 接着它将mRead置为true,这里想不用管,只知道她在初始化的时候是false就行了。接着往下,判断了一个mSetupPending属性,这个属性因为在一开是false的,所以不会进入括号内,所以更不会调用我们的setup()方法。难道这样就完了吗?不会,因为我们在XML中没有设置图片相关信息,那么我们肯定要在代码里边设置了,那么我们在代码里就需要绑定xml中的View然后调用circleImageView.setImageResource(R.mipmap.psb);我们在上一节中说过在CircleImageView中用四个方法可以设置图片,这个就是其中之一,好吧,我们接着看它的内部实现:

@Override
public void setImageResource(@DrawableRes int resId) {
    super.setImageResource(resId);
    initializeBitmap();
}

在代码中调用父类的setImageResource()方法设置图片,并调用initializeBitmap()方法,继续看:

private void initializeBitmap() {
    if (mDisableCircularTransformation) {
        mBitmap = null;
    } else {
        mBitmap = getBitmapFromDrawable(getDrawable());
    }
    setup();
}

判断是否禁止圆形属性,禁止mBitmap为null,不禁止获取到我们设置的Drawable并通过getBitmapFromDrawable()方法转换成mBitmap,然后调用setup()方法,看setup()方法:

private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (mBitmap == null) {
            invalidate();
            return;
        }
//        TileMode:(一共有三种)
//        CLAMP  :如果渲染器超出原始边界范围,会复制范围内边缘染色。
//        REPEAT :横向和纵向的重复渲染器图片,平铺。
//        MIRROR :横向和纵向的重复渲染器图片,这个和REPEAT重复方式不一样,他是以镜像方式平铺。
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //抗锯齿
        mBitmapPaint.setAntiAlias(true);

        mBitmapPaint.setShader(mBitmapShader);

        //Paint.Style.FILL:填充内部
        //Paint.Style.FILL_AND_STROKE  :填充内部和描边
        //Paint.Style.STROKE  :描边
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mFillPaint.setStyle(Paint.Style.FILL);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);
        //取的原图片的宽高
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
        mBorderRect.set(calculateBounds());
        //计算整个圆形带Border部分的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
        //初始图片显示区域为mBorderRect(CircleImageView中图片区域的实际大小)
        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            //到现在图片区域Rect(mDrawableRect)与整个View所用Rect(mBorderRadius)相同【mDrawableRect.set(mBorderRect)设置】,
            //如果在xml中设置app:civ_border_overlay="false"(边框不覆盖图片)并且外框宽度大于0,将图片显示区域Rect向内(缩小)mBorderWidth-1.0f。
            // inset()方法参数为正数表示缩小,为复数表示扩大区域。
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
        }
        //计算内圆最小半径,即去除边框后的Rect(内部图片Rect->mDrawableRect)宽度的半径
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

看一下,很长,没错,这个方法是当前类里最主要的一个方法,先说一下它做了几件事:

1.上来三个判断,一会说。

2.设置三个重要的paint及mBitmapPaint(画内部圆形图片用到的Paint)、mBorderpaint(画外部圆环用到的paint)、mFillPaint(画图片背景用到的paint)。

3.设置mBorderRect(外部圆环所占矩形区域)mBorderRadius(外部圆环半径)、mDrawableRect(内部图片所占矩形区域)、mDrawableRaduis(内部圆形图片半径)。

4.设置颜色过滤器。

5.设置BitmapShader的Matrix,设置缩放比,平移。

6.调用invaladate()刷新界面

这就是在setup()方法中干的几件事情,下面我们详细说明,回到代码,首先是三个判断,第一个判断mReady,因为我们构造函数中已经将其变成了true,所以不会进入内部,而是继续向下走。这里进一段小插曲,到目前为止,肯定很多人不明白,这个mReady及内部的mSetupPending 是干什么用的,这里说明一下,回到前边说的在XML中引入,但是没有设置android:src属性,以上都是它的执行顺序,那么,我们换另一种方式,及在XML文件中加入android:src属性,运行代码,你会发现,我们四个设置图片方法的第一个方法(setImageBitmap()方法)会被执行,而且是在构造方法以前执行,我们知道,在它里边也间接的调用了我们的setup()方法,但是此时我们的构造函数还没有执行,也就是说一些参数还没有被初始化,所以现在肯定是不能进行后续操作的,所以在这种情况下,当执行到setup()方法的时候第一个mReady(初始化为false)判断是过不去的,只是把mSetupPending设置成了true,然后return。接着才会执行我们的构造函数,在构造函数里边同样有一个关于mReadymSetuppending的操作,在init()中,

private void init() {
    super.setScaleType(SCALE_TYPE);
    mReady = true;

    if (mSetupPending) {
        setup();
        mSetupPending = false;
    }
}

因为我们前边在setImageBitmap()中将mSetupPending设置为了true,所以会进入setup()方法,说了这么多,不知道你听懂了没,多想多看几遍,相信你肯定能明白设计mReady、与mSetupPending的意义,就是在不同的情况下保证程序以正确的方式进行逻辑处理。多看几遍,只能帮你到这了。

插曲还挺长,接着看我们setup()中的代码,后续两个判断一个当前View宽高为0退出,一个没有获取到mBitmap退出,没什么好说的。接着往下走:

TileMode:(一共有三种)
//        CLAMP  :如果渲染器超出原始边界范围,会复制范围内边缘染色。
//        REPEAT :横向和纵向的重复渲染器图片,平铺。
//        MIRROR :横向和纵向的重复渲染器图片,这个和REPEAT重复方式不一样,他是以镜像方式平铺。

mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//抗锯齿
mBitmapPaint.setAntiAlias(true);

mBitmapPaint.setShader(mBitmapShader);

//Paint.Style.FILL:填充内部
//Paint.Style.FILL_AND_STROKE  :填充内部和描边
//Paint.Style.STROKE  :描边
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);

mFillPaint.setStyle(Paint.Style.FILL);
mFillPaint.setAntiAlias(true);
mFillPaint.setColor(mFillColor);

相信一些设置画笔的没什么好说的吧。看一下设置矩形跟半径相关的吧,如下:

//取的原图片的宽高
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(calculateBounds());
//计算整个圆形带Border部分的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
//初始图片显示区域为mBorderRect(CircleImageView中图片区域的实际大小)
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay && mBorderWidth > 0) {
    //到现在图片区域Rect(mDrawableRect)与整个View所用Rect(mBorderRadius)相同【mDrawableRect.set(mBorderRect)设置】,
    //如果在xml中设置app:civ_border_overlay="false"(边框不覆盖图片)并且外框宽度大于0,将图片显示区域Rect向内(缩小)mBorderWidth-1.0f。
    // inset()方法参数为正数表示缩小,为复数表示扩大区域。
    mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}
//计算内圆最小半径,即去除边框后的Rect(内部图片Rect->mDrawableRect)宽度的半径
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

可以看到在设置外环矩形时调用了一个calculateBounds()方法看看里边的实现:

private RectF calculateBounds() {
    //获取当前CircleImageView视图除去PaddingLeft与PaddingRight后剩余的可用宽度
    // (如果你设置的PaddingLeft+PaddingRight>+当前控件的宽度,当前控件会显示不出来);
    int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
    //获取当前CircleImageView视图除去PaddingTop与PaddingBottom后剩余的可用高度;
    int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
    //获取除去Padding后宽高剩余可用空间较小的一个值。
    int sideLength = Math.min(availableWidth, availableHeight);
    //如果最后得到的availableWidth与availableHeight不一样(我们在代码中设置的原因),大的要向小的靠齐,
    // 最终得到的RectF为正方形。
    float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
    float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
    return new RectF(left, top, left + sideLength, top + sideLength);
}

代码我已经加好了注释,多看几遍。设置好圆环矩形后,计算整个圆形带Border部分的最小半径,注意这里计算半径时宽高需要减去mBorderWidth再除以2,取mBorderRect的宽高减去一个边缘大小的一半的较小值做为半径。然后将mBorderRect设置给mDrawableRect,然后判断我们是否设置了圆环压住圆形图片并且mBorderWidth>0,如下:

if (!mBorderOverlay && mBorderWidth > 0) {
    mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}

如果都满足,则mDrawableRect的x,y都缩小(mBorderWidth-1.0f),然后计算出mDrawableRaduis。

然后调用了applyColorFilter()方法,看一下:

private void applyColorFilter() {
    if (mBitmapPaint != null) {
        mBitmapPaint.setColorFilter(mColorFilter);
    }
}

可以看到,就是给mBitmapPaint设置了mColorFiter,mColorFiter是通过上一节中的public方法设置的,如果我们没有设置,mColorFilter为null。

然后是我们的updateShaderMatrix()这个方法也很重要,看一下:

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) {
        scale = mDrawableRect.height() / (float) mBitmapHeight;
        dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
    } else {
        scale = mDrawableRect.width() / (float) mBitmapWidth;
        dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
    }
    //设置缩放比
    mShaderMatrix.setScale(scale, scale);
    //平移操作,(dx + 0.5f)的处理,是四舍五入
    mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

    mBitmapShader.setLocalMatrix(mShaderMatrix);
}

东西不多,但是重要啊,在计算缩放比scale时有一行代码不好理解,也转换了一下,好理解一点了,这里可以自己画一个图理解一下,下面是我举得一个例子,你可以对着我画的图,理解一下:


mShaderMatrix按照算出来的scale进行缩放,并进行相应的平移,最后赋给mBitmapShadermBitmapShadersetup()方法中已经付给了mBitmapPaint。最后就是调用invaladate()刷新界面了,调用invaladate()会执行onDraw()方法,下边看一下:

@Override
protected void onDraw(Canvas canvas) {
    // 是否允许转换成圆形设置
    if (mDisableCircularTransformation) {
        super.onDraw(canvas);
        return;
    }

    if (mBitmap == null) {
        return;
    }

    //如果设置了图片底色,绘制图片底色。
    if (mFillColor != Color.TRANSPARENT) {
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
    }
    //画内部图片区域(我们给mBitmapPaint设置了Shader,给Shader设置了LocalMatrix,通过ShaderMatrix设置了缩放比,及平移操作完成功能);
    canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
    //如果设置了BorderWidth宽度,绘制;
    if (mBorderWidth > 0) {
        canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
    }
}

OnDraw()方法很简单,就是用我们在setup()方法中设置的画笔进行绘画。在我们的上一节中的一些共有方法中会看到,其实很多方法都调用了invaladate()或者是setup()方法,对view进行了重新绘制。

 

到此,我们的代码就讲解完了,不知道你对CircleImageView的实现更加了解了没,如果看一遍看不明白,多看几遍。

 

4.用到的知识点的总计

通过源码分析我们可以知道,代码中作者用到了下边一些东西辅助完成功能:

1.ImageView.ScaleType

2.RectF

3.Matrix

4.Paint

5.BitmapShader

6.ColorFilter

 

我已将我加好详细注释的整个Demo文档上传至CSDN,你可以在下边连接进行下载:

http://download.csdn.net/detail/liuyonglei1314/9754395 

 

好了,到现在我们本篇文章就该结束了,希望对您有所帮助,谢谢,如果哪里写的不对,希望留言指正!


  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值