Android源码分析--CircleImageView 源码详解
2015-07-06 15:37
615 查看
源码地址为 https://github.com/hdodenhof/CircleImageView
实际上就是一个圆形的imageview 的自定义控件。代码写的很优雅,实现效果也很好,
特此分析。源码其实不难 主要就是一个类,可以把我的这个加了注释的源码放到你自己的工程里直接替换
然后run,这样效果更佳。
实际上就是一个圆形的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); } }
相关文章推荐
- cordova-android源代码分析
- Android 开源项目android-open-project解析之(三) ScrollView,TimeView,TipView,FlipView
- Android仿QQ侧滑效果
- android 常用小知识
- 指纹识别技术的基本原理及过程
- Android中Spinner下拉列表(使用ArrayAdapter和自定义Adapter实现)
- Android 开源项目android-open-project解析之(二) GridView,ImageView,ProgressBar,TextView
- AndroidStudio 添加svn插件
- android 基于百度地图api获取经纬度
- Android开发快捷键
- 增加录像时间戳水印、 camera框架介绍
- Android 开源项目android-open-project解析之(一) ListView,ActionBar,Menu,ViewPager,Gallery
- Android Studio使用总结
- android学习路线:如何成长为高级工程师
- android手机获取gps和基站的经纬度地址实现代码
- Android手机中的2048游戏Demo开发
- android studio使用问题汇总
- Android排错:has leaked window com.android.internal.policy.impl.PhoneWindow$ that was or
- Android使用xutils图片上传和服务器接收
- 解决android照片溢出问题