源码地址为 https://github.com/hdodenhof/CircleImageView

实际上就是一个圆形的imageview 的自定义控件。代码写的很优雅,实现效果也很好,

特此分析。源码其实不难 主要就是一个类,可以把我的这个加了注释的源码放到你自己的工程里直接替换

然后run,这样效果更佳。

 package de.hdodenhof.circleimageview;

 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.util.Log;
import android.widget.ImageView; /**
* 实际上整体思路还是比较简单的,利用BitmapShader 来把imageview里的图片分割成圆形
* 画出圆形来以后 再画描边。
* 这个开源控件做的比较出色的地方是updateShaderMatrix 函数会帮忙做图片修正,使切割出来的图片损失度最小.
* 此外就是各种情况考虑的比较多,流程控制的比较严谨,其中主要是多次调用setup函数 来完成imageview的及时刷新
*/
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);
Log.v("CircleImageView", "gou zao han shu");
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); //取得我们在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); a.recycle(); init();
} /**
* 这个函数 只在构造函数里面调用 他的作用就是 保证setup函数里的流程一定要在
* 构造函数执行完毕的时候去调用 mReady为true setup函数里的代码才能向下执行
*/
private void init() {
Log.v("CircleImageView", "init()");
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) {
Log.v("CircleImageView", "onDraw");
if (getDrawable() == null) {
return;
} //这行代码就是把imageview 切割成最终的圆形
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
//如果圆形边缘的宽度不为0 我们还要继续画这个描边
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();
} @Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
setup();
} /**
* 注意这个函数 是在我们的构造函数调用之前就调用了
*
* @param drawable
*/
@Override
public void setImageDrawable(Drawable drawable) {
Log.v("CircleImageView", "setImageDrawable Drawable");
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
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();
} private Bitmap getBitmapFromDrawable(Drawable drawable) {
Log.v("CircleImageView", "getBitmapFromDrawable");
if (drawable == null) {
Log.v("CircleImageView", "drawable==null");
//這種情況一般不會發生
return null;
} if (drawable instanceof BitmapDrawable) {
Log.v("CircleImageView", "drawable instanceof BitmapDrawable");
//通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
return ((BitmapDrawable) drawable).getBitmap();
}
Log.v("CircleImageView", "drawable is not instanceof BitmapDrawable"); try {
Bitmap bitmap; if (drawable instanceof ColorDrawable) {
Log.v("CircleImageView", "drawable instanceof ColorDrawable");
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
Log.v("CircleImageView", "drawable is not instanceof ColorDrawable");
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;
}
} /**
* 这个函数比较关键,就是在进行一些重绘参数的初始化
*/
private void setup() {
Log.v("CircleImageView", "setup()");
Log.v("CircleImageView", "mReady==" + mReady + " mSetupPending==" + mSetupPending);
//这个地方要注意mReady的默认值为false,也就是说第一次进这个函数的时候 因为为false 所以直接进入括号
//体内然后返回,后面的代码并没有执行。 同时也能知道,mReady的值更改成true 是在init函数里面做的
if (!mReady) {
mSetupPending = true;
return;
} //防止空指针异常
if (mBitmap == null) {
return;
} //参数值就代表 如果图片太小的话 就直接拉伸,repeat参数代表 图片大小的话就重复放图片 mirror就是镜像对着放图片的意思 跟大家设置pc 屏保时候其实是一样的
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(); //注意这个地方取的是imageview的实际大小,也就是说这个地方画了一个和imageview实际大小一致的方形图
mBorderRect.set(0, 0, getWidth(), getHeight());
Log.v("CircleImageView", "mBitmapHeight==" + mBitmapHeight + " mBitmapWidth==" + mBitmapWidth);
Log.v("CircleImageView", "getWidth()" + getWidth() + " getHeight()==" + getHeight());
//这个地方就是算最小半径的,注意是要减去边缘宽度的 因为这里计算的是 圆形边缘部分的最小半径
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2); mDrawableRect.set(mBorderRect);
if (!mBorderOverlay) {
mDrawableRect.inset(mBorderWidth, mBorderWidth);
}
//这里计算的是圆形内部的最小半径,其实很好理解,因为这个自定义控件提供了设置圆形边缘宽度的属性方法,所以在这里对于一个圆形边缘有宽度的图形来说
//半径就是有2个,一个是外部半径,一个内部半径,上面的mBorderRadius就是内部半径 而这里是外部半径 一般来说 mDrawableRadius>=mBorderRadius
mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
updateShaderMatrix();
//手动触发ondraw()函数 完成最终的绘制
invalidate();
} /**
* 这个函数很好理解,就是做平移变换 放大或者缩小图片 所使用的,尽量保证 我们切割出来的图片 损失度最小。‘
* <p/>
* 这里面的算法可以好好研读一下 此方法能保证你每次切割出来的图片都是 原始图片正中央的那一部分
*/
private void updateShaderMatrix() {
Log.v("CircleImageView", "updateShaderMatrix()");
float scale;
float dx = 0;
float dy = 0; mShaderMatrix.set(null); if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { //此缩放策略就是y轴缩放 x轴平移
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
//此缩放策略是 x轴缩放 y轴平移
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
} mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); mBitmapShader.setLocalMatrix(mShaderMatrix);
} }

Android源码分析--CircleImageView 源码详解的更多相关文章

  1. 死磕 java并发包之AtomicStampedReference源码分析(ABA问题详解)

    问题 (1)什么是ABA? (2)ABA的危害? (3)ABA的解决方法? (4)AtomicStampedReference是什么? (5)AtomicStampedReference是怎么解决AB ...

  2. Laravel源码分析--Laravel生命周期详解

    一.XDEBUG调试 这里我们需要用到php的 xdebug 拓展,所以需要小伙伴们自己去装一下,因为我这里用的是docker,所以就简单介绍下在docker中使用xdebug的注意点. 1.在php ...

  3. spring源码分析之spring-web http详解

    spring-web是spring webmvc的基础,它的功能如下: 1. 封装http协议中client端/server端的request请求和response响应及格式的转换,如json,rss ...

  4. spring源码分析之spring-jms模块详解

    0 概述 spring提供了一个jms集成框架,这个框架如spring 集成jdbc api一样,简化了jms api的使用. jms可以简单的分成两个功能区,消息的生产和消息的消费.JmsTempl ...

  5. spring源码分析之spring-messaging模块详解

    0 概述 spring-messaging模块为集成messaging api和消息协议提供支持. 其代码结构为: 其中base定义了消息Message(MessageHeader和body).消息处 ...

  6. spring源码分析之spring-jdbc模块详解

    0 概述 Spring将替我们完成所有使用JDBC API进行开发的单调乏味的.底层细节处理工作.下表描述了哪些是spring帮助我们做好的,哪些是我们要做的. Action  Spring  You ...

  7. [源码分析] 从源码入手看 Flink Watermark 之传播过程

    [源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...

  8. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  9. k8s client-go源码分析 informer源码分析(2)-初始化与启动分析

    k8s client-go源码分析 informer源码分析(2)-初始化与启动分析 前面一篇文章对k8s informer做了概要分析,本篇文章将对informer的初始化与启动进行分析. info ...

随机推荐

  1. 在 eclipse 中设置每行的字数

    在Preferences中:Java Code Style Formatter

  2. 叠罗汉I

    叠罗汉是一个著名的游戏,游戏中一个人要站在另一个人的肩膀上.同时我们应该让下面的人比上面的人更高一点.已知参加游戏的每个人的身高,请编写代码计算通过选择参与游戏的人,我们多能叠多少个人.注意这里的人都 ...

  3. linux查询cpu核心数

    linux怎么查询cpu核心数 1.查看逻辑CPU个数: #cat /proc/cpuinfo |grep "processor"|sort -u|wc -l24 2.由于有超线程 ...

  4. CentOS下支持exFAT与NTFS

    exFAT: 1.下载fuse-exfat支持软件: exfat支持是通过fuse模块的方式支持的,其项目地址是: https://code.google.com/p/exfat/ ,当前版本是:1. ...

  5. Objective-C:自定义Block函数

    Block函数是一种类似于函数指针的函数,程序员只需要把需要操作的代码封装到定义的block中即可,以后需要使用时,直接调用,非常方便.... 举例如下: 第一种形式:自定义一个无返回值而且无参数的b ...

  6. Android 广播机制(两种注册方法)与中断广播

    两种注册类型的区别是: 1)第一种不是常驻型广播,也就是说广播跟随activity的生命周期.注意: 在activity结束前,移除广播接收器. 2)第二种是常驻型,也就是说当应用程序关闭后,如果有信 ...

  7. PHP 对象及其三大特性

    //面向过程 //类和对象 //对象:任何东西都可以成为对象,类实例化出来的东西 //类:对所有同类的对象抽象出来的东西 //info:code,name,sex,nation,birthday // ...

  8. Linux内核等待队列

    在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待等列中取出进程. Linux 2.6内核提供了如下关于 ...

  9. JS实现 页面提交防刷新等待提示

    //关闭等待窗口 function closediv() { //Close Div document.body.removeChild(document.getElementById("b ...

  10. iOS 7 如何关闭已打开的应用(App)

    刚升级了 iOS 7,感觉不太会用了. 在多任务状态下,看着一个个已被打开的应用,不知道如何关闭了. 问了下朋友才知道,在多任务状态下,将对应的应用 向上划 就行. 听说,和 Android 一样的 ...