circleimageview

作者: 穿越平行宇宙 | 来源:发表于2019-04-15 20:07 被阅读11次

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

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

    dependencies {
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'de.hdodenhof:circleimageview:2.1.0'
    }

    下面我们讲一下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进行绑定,运行项目就会看到如下结果:

    20170214174328180.png

    我们还可以在代码中对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。接着才会执行我们的构造函数,在构造函数里边同样有一个关于mReady与mSetuppending的操作,在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进行缩放,并进行相应的平移,最后赋给mBitmapShader,mBitmapShader在setup()方法中已经付给了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

    相关文章

      网友评论

        本文标题:circleimageview

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