您的位置:首页 > 移动开发 > Android开发

Android源码分析--CircleImageView 源码详解

2015-07-06 15:37 615 查看
源码地址为 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);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: