http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0806/3268.html

  1. tools:context="com.example.circleimageviewdemo_1.MainActivity" >
  2. <RelativeLayout
  3. android:layout_width="match_parent"
  4. android:layout_height="0dp"
  5. android:layout_weight="1"
  6. android:padding="@dimen/base_padding"
  7. android:background="@color/light">
  8. <com.example.library.CircleImageView
  9. android:layout_width="160dp"
  10. android:layout_height="160dp"
  11. android:layout_centerInParent="true"
  12. android:src="@drawable/hugh"
  13. app:border_width="2dp"
  14. app:border_color="@color/dark" />
  15. </RelativeLayout>
  16. <RelativeLayout
  17. android:layout_width="match_parent"
  18. android:layout_height="0dp"
  19. android:layout_weight="1"
  20. android:padding="@dimen/base_padding"
  21. android:background="@color/dark">
  22. <com.example.library.CircleImageView
  23. android:layout_width="160dp"
  24. android:layout_height="160dp"
  25. android:layout_centerInParent="true"
  26. android:src="@drawable/hugh"
  27. app:border_width="2dp"
  28. app:border_color="@color/light" />
  29. </RelativeLayout>
  30. </LinearLayout>

注意两点:

  • 在xml布局文件中,声明我们的自定义View,路径要正确。 
    com.example.library.CircleImageView

  • 一定要引入命名空间,xmlns:app=”http://schemas.android.com/apk/res-auto”,不然无法使用自定义属性。

MainActivity里不用修改,因为我直接在XML里面用src设置了图片,你也可以在MainActivity中用setImageXXX设置。直接运行看效果。

总结下CircleImageView的使用:

  1. 将CircleImageView.java拷贝到项目工程中

  2. attrs.xml拷贝到res/values/目录下

  3. 在布局文件中声明自定义View,一定要引入命名空间xmlns:app=”http://schemas.android.com/apk/res-auto”。

Demo下载: 
http://download.csdn.net/detail/zhoubin1992/8956647


CircleImageView源码分析

一直很好奇这个开源库是怎么自定义圆形ImageView,结果最近花了很多功夫进行分析,现在也非常迫切想整理出来,希望能帮到一些人。我也看了网络上的一些源码分析,有些分析是错误的会误导大家,有些说的不详细还是无法理解。 
首先我先总结下CircleImageView的主要流程,让大家有个整体把握。然后再一步步进入源码分析。

CircleImageView的主要流程:

1. 首先通过setImageXxx()方法设置图片Bitmap; 
2. 进入构造函数CircleImageView()获取自定义参数,以及调用setup()函数; 
3. 进入setup()函数(非常关键),进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式和内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数; 
4. 进入updateShaderMatrix()函数,计算缩放比例和平移,设置BitmapShader的Matrix参数等; 
5. 触发ondraw()函数完成最终的绘制。使用配置好的Paint先画出绘制内圆形来以后再画边界圆形。


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

那我们看下源码:

  1. /**
  2. * 以下四个函数都是
  3. * 复写ImageView的setImageXxx()方法
  4. * 注意这个函数先于构造函数调用之前调用
  5. * @param bm
  6. */
  7. @Override
  8. public void setImageBitmap(Bitmap bm) {
  9. super.setImageBitmap(bm);
  10. mBitmap = bm;
  11. setup();
  12. }
  13. @Override
  14. public void setImageDrawable(Drawable drawable) {
  15. super.setImageDrawable(drawable);
  16. mBitmap = getBitmapFromDrawable(drawable);
  17. System.out.println("setImageDrawable -- setup");
  18. setup();
  19. }
  20. @Override
  21. public void setImageResource(@DrawableRes int resId) {
  22. super.setImageResource(resId);
  23. mBitmap = getBitmapFromDrawable(getDrawable());
  24. setup();
  25. }
  26. @Override
  27. public void setImageURI(Uri uri) {
  28. super.setImageURI(uri);
  29. mBitmap = getBitmapFromDrawable(getDrawable());
  30. setup();
  31. }

看上面代码会发现,四个函数都是获取图片Bitmap,调用setup()。 
接下来那我们查看setup()源码:

  1. //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
  2. //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
  3. if (!mReady) {
  4. mSetupPending = true;
  5. return;
  6. }

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


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

  1. /**
  2. * 构造函数
  3. */
  4. public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
  5. super(context, attrs, defStyle);
  6. //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
  7. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
  8. //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
  9. // 获取边界的宽度
  10. mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
  11. // 获取边界的颜色
  12. mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
  13. mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
  14. //调用 recycle() 回收TypedArray,以便后面重用
  15. a.recycle();
  16. System.out.println("CircleImageView -- 构造函数");
  17. init();
  18. }

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

  1. /**
  2. * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用
  3. */
  4. private void init() {
  5. //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。
  6. super.setScaleType(SCALE_TYPE);
  7. mReady = true;
  8. if (mSetupPending) {
  9. setup();
  10. mSetupPending = false;
  11. }
  12. }

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


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

  1. /**
  2. * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
  3. * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
  4. * 以及调用updateShaderMatrix()函数和 invalidate()函数;
  5. */
  6. private void setup() {
  7. //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
  8. //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
  9. if (!mReady) {
  10. mSetupPending = true;
  11. return;
  12. }
  13. //防止空指针异常
  14. if (mBitmap == null) {
  15. return;
  16. }
  17. // 构建渲染器,用mBitmap来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
  18. mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
  19. // 设置图片画笔反锯齿
  20. mBitmapPaint.setAntiAlias(true);
  21. // 设置图片画笔渲染器
  22. mBitmapPaint.setShader(mBitmapShader);
  23. // 设置边界画笔样式
  24. mBorderPaint.setStyle(Paint.Style.STROKE);
  25. mBorderPaint.setAntiAlias(true);
  26. mBorderPaint.setColor(mBorderColor);    //画笔颜色
  27. mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度
  28. //这个地方是取的原图片的宽高
  29. mBitmapHeight = mBitmap.getHeight();
  30. mBitmapWidth = mBitmap.getWidth();
  31. // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸
  32. mBorderRect.set(0, 0, getWidth(), getHeight());
  33. //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
  34. mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
  35. // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
  36. mDrawableRect.set(mBorderRect);
  37. if (!mBorderOverlay) {
  38. //demo里始终执行
  39. //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)
  40. mDrawableRect.inset(mBorderWidth, mBorderWidth);
  41. }
  42. //这里计算的是内圆的最小半径,也即去除边界宽度的半径
  43. mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
  44. //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
  45. updateShaderMatrix();
  46. //手动触发ondraw()函数 完成最终的绘制
  47. invalidate();
  48. }

上面代码注释我写的很详细不再一步步解释了,进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数。 
这里关于半径的计算,我画图举个例子:CircleImageView的布局宽高度均为160,边界的宽度为10如图所示:

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


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

  1. /**
  2. * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。
  3. * 作用:保证图片损失度最小和始终绘制图片正中央的那部分
  4. */
  5. private void updateShaderMatrix() {
  6. float scale;
  7. float dx = 0;
  8. float dy = 0;
  9. mShaderMatrix.set(null);
  10. // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
  11. //取最小的缩放比例
  12. if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
  13. //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
  14. scale = mDrawableRect.height() / (float) mBitmapHeight;
  15. dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
  16. } else {
  17. //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
  18. scale = mDrawableRect.width() / (float) mBitmapWidth;
  19. dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
  20. }
  21. // shaeder的变换矩阵,我们这里主要用于放大或者缩小。
  22. mShaderMatrix.setScale(scale, scale);
  23. // 平移
  24. mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
  25. // 设置变换矩阵
  26. mBitmapShader.setLocalMatrix(mShaderMatrix);
  27. }

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

  1. if判断语句里
  2. mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight
  3. 写成
  4. (mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
  5. 更好理解。

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


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

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. //如果图片不存在就不画
  4. if (getDrawable() == null) {
  5. return;
  6. }
  7. //绘制内圆形,参数内圆半径,图片画笔为mBitmapPaint
  8. canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
  9. //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 参数外圆半径,边界画笔为mBorderPaint
  10. if (mBorderWidth != 0) {
  11. canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
  12. }
  13. }

使用配置好的mBitmapPaint和mBorderPaint先画出绘制内圆形来以后再画边界圆形。源码还有一些自定义设置样式函数,很简单。 
大功告成,是不是觉得思路比较简单,精致干练。

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

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

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

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


附上完整源码:

  1. package com.example.library;
  2. import com.example.circleimageviewdemo_1.R;
  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.graphics.Bitmap;
  6. import android.graphics.BitmapShader;
  7. import android.graphics.Canvas;
  8. import android.graphics.Color;
  9. import android.graphics.ColorFilter;
  10. import android.graphics.Matrix;
  11. import android.graphics.Paint;
  12. import android.graphics.RectF;
  13. import android.graphics.Shader;
  14. import android.graphics.drawable.BitmapDrawable;
  15. import android.graphics.drawable.ColorDrawable;
  16. import android.graphics.drawable.Drawable;
  17. import android.net.Uri;
  18. import android.support.annotation.ColorRes;
  19. import android.support.annotation.DrawableRes;
  20. import android.util.AttributeSet;
  21. import android.widget.ImageView;
  22. /**
  23. * 流程控制的比较严谨,比如setup函数的使用
  24. * updateShaderMatrix保证图片损失度最小和始终绘制图片正中央的那部分
  25. * 作者思路是画圆用渲染器位图填充,而不是把Bitmap重绘切割成一个圆形图片。
  26. */
  27. public class CircleImageView extends ImageView {
  28. //缩放类型
  29. private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
  30. private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
  31. private static final int COLORDRAWABLE_DIMENSION = 2;
  32. // 默认边界宽度
  33. private static final int DEFAULT_BORDER_WIDTH = 0;
  34. // 默认边界颜色
  35. private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
  36. private static final boolean DEFAULT_BORDER_OVERLAY = false;
  37. private final RectF mDrawableRect = new RectF();
  38. private final RectF mBorderRect = new RectF();
  39. private final Matrix mShaderMatrix = new Matrix();
  40. //这个画笔最重要的是关联了mBitmapShader 使canvas在执行的时候可以切割原图片(mBitmapShader是关联了原图的bitmap的)
  41. private final Paint mBitmapPaint = new Paint();
  42. //这个描边,则与本身的原图bitmap没有任何关联,
  43. private final Paint mBorderPaint = new Paint();
  44. //这里定义了 圆形边缘的默认宽度和颜色
  45. private int mBorderColor = DEFAULT_BORDER_COLOR;
  46. private int mBorderWidth = DEFAULT_BORDER_WIDTH;
  47. private Bitmap mBitmap;
  48. private BitmapShader mBitmapShader; // 位图渲染
  49. private int mBitmapWidth;   // 位图宽度
  50. private int mBitmapHeight;  // 位图高度
  51. private float mDrawableRadius;// 图片半径
  52. private float mBorderRadius;// 带边框的的图片半径
  53. private ColorFilter mColorFilter;
  54. //初始false
  55. private boolean mReady;
  56. private boolean mSetupPending;
  57. private boolean mBorderOverlay;
  58. //构造函数
  59. public CircleImageView(Context context) {
  60. super(context);
  61. init();
  62. }
  63. //构造函数
  64. public CircleImageView(Context context, AttributeSet attrs) {
  65. this(context, attrs, 0);
  66. }
  67. /**
  68. * 构造函数
  69. */
  70. public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
  71. super(context, attrs, defStyle);
  72. //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
  73. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
  74. //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
  75. // 获取边界的宽度
  76. mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
  77. // 获取边界的颜色
  78. mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
  79. mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
  80. //调用 recycle() 回收TypedArray,以便后面重用
  81. a.recycle();
  82. System.out.println("CircleImageView -- 构造函数");
  83. init();
  84. }
  85. /**
  86. * 作用就是保证第一次执行setup函数里下面代码要在构造函数执行完毕时调用
  87. */
  88. private void init() {
  89. //在这里ScaleType被强制设定为CENTER_CROP,就是将图片水平垂直居中,进行缩放。
  90. super.setScaleType(SCALE_TYPE);
  91. mReady = true;
  92. if (mSetupPending) {
  93. setup();
  94. mSetupPending = false;
  95. }
  96. }
  97. @Override
  98. public ScaleType getScaleType() {
  99. return SCALE_TYPE;
  100. }
  101. /**
  102. * 这里明确指出 此种imageview 只支持CENTER_CROP 这一种属性
  103. *
  104. * @param scaleType
  105. */
  106. @Override
  107. public void setScaleType(ScaleType scaleType) {
  108. if (scaleType != SCALE_TYPE) {
  109. throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
  110. }
  111. }
  112. @Override
  113. public void setAdjustViewBounds(boolean adjustViewBounds) {
  114. if (adjustViewBounds) {
  115. throw new IllegalArgumentException("adjustViewBounds not supported.");
  116. }
  117. }
  118. @Override
  119. protected void onDraw(Canvas canvas) {
  120. //如果图片不存在就不画
  121. if (getDrawable() == null) {
  122. return;
  123. }
  124. //绘制内圆形 图片 画笔为mBitmapPaint
  125. canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
  126. //如果圆形边缘的宽度不为0 我们还要绘制带边界的外圆形 边界画笔为mBorderPaint
  127. if (mBorderWidth != 0) {
  128. canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
  129. }
  130. }
  131. @Override
  132. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  133. super.onSizeChanged(w, h, oldw, oldh);
  134. setup();
  135. }
  136. public int getBorderColor() {
  137. return mBorderColor;
  138. }
  139. public void setBorderColor(int borderColor) {
  140. if (borderColor == mBorderColor) {
  141. return;
  142. }
  143. mBorderColor = borderColor;
  144. mBorderPaint.setColor(mBorderColor);
  145. invalidate();
  146. }
  147. public void setBorderColorResource(@ColorRes int borderColorRes) {
  148. setBorderColor(getContext().getResources().getColor(borderColorRes));
  149. }
  150. public int getBorderWidth() {
  151. return mBorderWidth;
  152. }
  153. public void setBorderWidth(int borderWidth) {
  154. if (borderWidth == mBorderWidth) {
  155. return;
  156. }
  157. mBorderWidth = borderWidth;
  158. setup();
  159. }
  160. public boolean isBorderOverlay() {
  161. return mBorderOverlay;
  162. }
  163. public void setBorderOverlay(boolean borderOverlay) {
  164. if (borderOverlay == mBorderOverlay) {
  165. return;
  166. }
  167. mBorderOverlay = borderOverlay;
  168. setup();
  169. }
  170. /**
  171. * 以下四个函数都是
  172. * 复写ImageView的setImageXxx()方法
  173. * 注意这个函数先于构造函数调用之前调用
  174. * @param bm
  175. */
  176. @Override
  177. public void setImageBitmap(Bitmap bm) {
  178. super.setImageBitmap(bm);
  179. mBitmap = bm;
  180. setup();
  181. }
  182. @Override
  183. public void setImageDrawable(Drawable drawable) {
  184. super.setImageDrawable(drawable);
  185. mBitmap = getBitmapFromDrawable(drawable);
  186. System.out.println("setImageDrawable -- setup");
  187. setup();
  188. }
  189. @Override
  190. public void setImageResource(@DrawableRes int resId) {
  191. super.setImageResource(resId);
  192. mBitmap = getBitmapFromDrawable(getDrawable());
  193. setup();
  194. }
  195. @Override
  196. public void setImageURI(Uri uri) {
  197. super.setImageURI(uri);
  198. mBitmap = getBitmapFromDrawable(getDrawable());
  199. setup();
  200. }
  201. @Override
  202. public void setColorFilter(ColorFilter cf) {
  203. if (cf == mColorFilter) {
  204. return;
  205. }
  206. mColorFilter = cf;
  207. mBitmapPaint.setColorFilter(mColorFilter);
  208. invalidate();
  209. }
  210. /**
  211. * Drawable转Bitmap
  212. * @param drawable
  213. * @return
  214. */
  215. private Bitmap getBitmapFromDrawable(Drawable drawable) {
  216. if (drawable == null) {
  217. return null;
  218. }
  219. if (drawable instanceof BitmapDrawable) {
  220. //通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
  221. return ((BitmapDrawable) drawable).getBitmap();
  222. }
  223. try {
  224. Bitmap bitmap;
  225. if (drawable instanceof ColorDrawable) {
  226. bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
  227. } else {
  228. bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
  229. }
  230. Canvas canvas = new Canvas(bitmap);
  231. drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
  232. drawable.draw(canvas);
  233. return bitmap;
  234. } catch (OutOfMemoryError e) {
  235. return null;
  236. }
  237. }
  238. /**
  239. * 这个函数很关键,进行图片画笔边界画笔(Paint)一些重绘参数初始化:
  240. * 构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式以及内外圆半径计算等,
  241. * 以及调用updateShaderMatrix()函数和 invalidate()函数;
  242. */
  243. private void setup() {
  244. //因为mReady默认值为false,所以第一次进这个函数的时候if语句为真进入括号体内
  245. //设置mSetupPending为true然后直接返回,后面的代码并没有执行。
  246. if (!mReady) {
  247. mSetupPending = true;
  248. return;
  249. }
  250. //防止空指针异常
  251. if (mBitmap == null) {
  252. return;
  253. }
  254. // 构建渲染器,用mBitmap位图来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
  255. mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
  256. // 设置图片画笔反锯齿
  257. mBitmapPaint.setAntiAlias(true);
  258. // 设置图片画笔渲染器
  259. mBitmapPaint.setShader(mBitmapShader);
  260. // 设置边界画笔样式
  261. mBorderPaint.setStyle(Paint.Style.STROKE);//设画笔为空心
  262. mBorderPaint.setAntiAlias(true);
  263. mBorderPaint.setColor(mBorderColor);    //画笔颜色
  264. mBorderPaint.setStrokeWidth(mBorderWidth);//画笔边界宽度
  265. //这个地方是取的原图片的宽高
  266. mBitmapHeight = mBitmap.getHeight();
  267. mBitmapWidth = mBitmap.getWidth();
  268. // 设置含边界显示区域,取的是CircleImageView的布局实际大小,为方形,查看xml也就是160dp(240px)  getWidth得到是某个view的实际尺寸
  269. mBorderRect.set(0, 0, getWidth(), getHeight());
  270. //计算 圆形带边界部分(外圆)的最小半径,取mBorderRect的宽高减去一个边缘大小的一半的较小值(这个地方我比较纳闷为什么求外圆半径需要先减去一个边缘大小)
  271. mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
  272. // 初始图片显示区域为mBorderRect(CircleImageView的布局实际大小)
  273. mDrawableRect.set(mBorderRect);
  274. if (!mBorderOverlay) {
  275. //demo里始终执行
  276. //通过inset方法  使得图片显示的区域从mBorderRect大小上下左右内移边界的宽度形成区域,查看xml边界宽度为2dp(3px),所以方形边长为就是160-4=156dp(234px)
  277. mDrawableRect.inset(mBorderWidth, mBorderWidth);
  278. }
  279. //这里计算的是内圆的最小半径,也即去除边界宽度的半径
  280. mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
  281. //设置渲染器的变换矩阵也即是mBitmap用何种缩放形式填充
  282. updateShaderMatrix();
  283. //手动触发ondraw()函数 完成最终的绘制
  284. invalidate();
  285. }
  286. /**
  287. * 这个函数为设置BitmapShader的Matrix参数,设置最小缩放比例,平移参数。
  288. * 作用:保证图片损失度最小和始终绘制图片正中央的那部分
  289. */
  290. private void updateShaderMatrix() {
  291. float scale;
  292. float dx = 0;
  293. float dy = 0;
  294. mShaderMatrix.set(null);
  295. // 这里不好理解 这个不等式也就是(mBitmapWidth / mDrawableRect.width()) > (mBitmapHeight / mDrawableRect.height())
  296. //取最小的缩放比例
  297. if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
  298. //y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
  299. scale = mDrawableRect.height() / (float) mBitmapHeight;
  300. dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
  301. } else {
  302. //x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
  303. scale = mDrawableRect.width() / (float) mBitmapWidth;
  304. dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
  305. }
  306. // shaeder的变换矩阵,我们这里主要用于放大或者缩小。
  307. mShaderMatrix.setScale(scale, scale);
  308. // 平移
  309. mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
  310. // 设置变换矩阵
  311. mBitmapShader.setLocalMatrix(mShaderMatrix);
  312. }
  313. }

Android自定义View总结

步骤 
1. 自定义View的属性 
2. 继承View,重写构造函数,获取我们自定义的属性值 
3. 重写onMesure()方法 
4. 重写onDraw()方法

自定义圆形头像CircleImageView的使用和源码分析的更多相关文章

  1. 【Android开源项目分析】自定义圆形头像CircleImageView的使用和源码分析

    原文地址: http://blog.csdn.net/zhoubin1992/article/details/47258639 效果

  2. Android 自定义圆形图片 CircleImageView

    1.效果预览 1.1.布局中写自定义圆形图片的路径即可 1.2.然后看一看图片效果 1.3.原图是这样的 @mipmap/ic_launcher 2.使用过程 2.1.CircleImageView源 ...

  3. Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸

    Android特效专辑(五)--自定义圆形头像和仿MIUI卸载动画-粒子爆炸 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆 ...

  4. Kubernetes Job Controller 原理和源码分析(一)

    概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...

  5. Kubernetes Job Controller 原理和源码分析(二)

    概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...

  6. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  7. Quartz学习--二 Hello Quartz! 和源码分析

    Quartz学习--二  Hello Quartz! 和源码分析 三.  Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...

  8. Android Debuggerd 简要介绍和源码分析(转载)

    转载: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E ...

  9. Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...

随机推荐

  1. MyBatis基础入门

    1.MyBatis概述 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动.创建connection.创 ...

  2. maven profile多环境动态配置文件使用

    pom.xml <profiles> <!-- =====开发环境====== --> <profile> <id>dev</id> < ...

  3. 160613、MyBatis insert操作返回主键

    在使用MyBatis做持久层时,insert语句默认是不返回记录的主键值,而是返回插入的记录条数:如果业务层需要得到记录的主键时,可以通过配置的方式来完成这个功能,针对Sequence主键而言,在执行 ...

  4. java模块开发关键步骤

    1. 创建数据表 a) 确定表名(如:role) b) 确定表中的业务列(如:role_name.role_desc) c) 添加其它基本列 i. 如:role_id(主键).status(数据状态, ...

  5. Java中break、continue、return语句的使用区别

    break.continue.return之间的区别与联系 在软件开发过程中,逻辑清晰是非常之重要的. 代码的规范也是非常重要的.往往细节决定成败.在编写代码的时候,一定要理解语言的作用以及使用的方法 ...

  6. Myeclipse中js总是报错

    1.右键选择 MyEclipse-->Exclude From Validation .2.再右键选择 MyEclipse-->Run Validation 即可.

  7. packages managers

    nodejs npm/bower/component ...rubygemsperl cpanpython pipOS X homebrewsublime text的package-control 那 ...

  8. Buy a home in AU

    澳洲留学生买房的几点注意事项: 1. 新房.楼花.或者买地建房,完全不受限制,国民待遇,是政府鼓励的. 2. 留学生签证剩余超12个月,可以购买二手房,但是只能自住不能出租. 3. 银行可以提供50% ...

  9. flask中db.init_app(app)讲解

    http://www.pythondoc.com/flask/extensiondev.html http://www.pythondoc.com/flask/extensiondev.html#fl ...

  10. 前端 Dom 直接选择器

    文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...