3.1Paint和Canvas
目录介绍
- 00.Paint和Canvas梳理
- 01.Paint画笔介绍
- 02.Canvas画布介绍
- 03.Matrix变换矩阵
- 04.Rect和RectF介绍
- 05.关于几个案例
00.Paint和Canvas梳理
00.1 Paint涉及方法
类别 | API | 描述 |
---|---|---|
颜色 | setColor, setARGB, setAlpha | 依次为设置画笔颜色、透明度 |
类型 | setStyle | 填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE) |
抗锯齿 | setAntiAlias | 画笔是否抗锯齿 |
字体大小 | setTextSize | 设置字体大小 |
字体测量 | getFontMetrics(),getFontMetricsInt() | 返回字体的各种测量值,返回值依次为float、int |
文字宽度测量 | measureText | 返回文字的宽度 |
文字对齐方式 | setTextAlign | 左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT) |
宽度 | setStrokeWidth | 设置画笔宽度 |
笔锋 | setStrokeCap | 默认(BUTT),半圆形(ROUND),方形(SQUARE) |
00.2 Canvas涉及方法
类别 | API | 描述 |
---|---|---|
绘制图形 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为绘制点、直线、矩形、圆角矩形、椭圆、圆、扇形 |
绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为绘制文字、指定每个字符位置绘制文字、根据路径绘制文字 |
画布变换 | translate, scale, rotate, skew | 依次为平移、缩放、旋转、倾斜(错切) |
画布裁剪 | clipPath, clipRect, clipRegion | 依次为按路径、按矩形、按区域对画布进行裁剪 |
画布状态 | sava,restore | 保存当前画布矩阵,恢复之前保存的画布 |
01.Paint画笔介绍
- Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色,样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。
1.1 Paint图形绘制
- 常用的方法有这些
setARGB(int a,int r,int g,int b); 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。 setAlpha(int a); 设置绘制图形的透明度。 setColor(int color); 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。 setAntiAlias(boolean aa); 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。 setDither(boolean dither); 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰 setFilterBitmap(boolean filter); 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置 setMaskFilter(MaskFilter maskfilter); 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 setColorFilter(ColorFilter colorfilter); 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果 setPathEffect(PathEffect effect); 设置绘制路径的效果,如点画线等 setShader(Shader shader); 设置图像效果,使用Shader可以绘制出各种渐变效果 setShadowLayer(float radius ,float dx,float dy,int color); 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色 setStyle(Paint.Style style); 设置画笔的样式,为FILL,FILL_AND_STROKE,或STROKE setStrokeCap(Paint.Cap cap); 当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE setSrokeJoin(Paint.Join join); 设置绘制时各图形的结合方式,如平滑效果等 setStrokeWidth(float width); 当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的粗细度 setXfermode(Xfermode xfermode); 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
1.2 Paint文本绘制
- 常用的方法有这些
setFakeBoldText(boolean fakeBoldText); 模拟实现粗体文字,设置在小字体上效果会非常差 setSubpixelText(boolean subpixelText); 设置该项为true,将有助于文本在LCD屏幕上的显示效果 setTextAlign(Paint.Align align); 设置绘制文字的对齐方向 setTextScaleX(float scaleX); 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果 setTextSize(float textSize); 设置绘制文字的字号大小 setTextSkewX(float skewX); 设置斜体文字,skewX为倾斜弧度 setTypeface(Typeface typeface); 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等 setUnderlineText(boolean underlineText); 设置带有下划线的文字效果 setStrikeThruText(boolean strikeThruText); 设置带有删除线的效果
2.Canvas画布介绍
- 当我们调整好画笔之后,现在需要绘制到画布上,这就得用Canvas类了。在android中既然把Canvas当做画布,那么就可以在画布上绘制我们想要的任何东西。除了在画布上绘制之外,还需要设置一些关于画布的属性,比如,画布的颜色、尺寸等。
2.1 设置属性
- 一般属性有:
Canvas(Bitmap bitmap): 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,因此bitmap不得为null。 Canvas(GL gl): 在绘制3D效果时使用,与OpenGL相关。 isOpaque(boolean isOpaque):检测是否支持透明。 setViewport(int left, int top, int right, int bottom, int clipflag): 设置画布中显示窗口。 drawColor(int color): 设置Canvas的背景颜色。 setBitmap(Bitmap mBitmap): 设置具体画布,画的内容,保存为一个Bitmap。 clipRect(float left, float top, float right, float bottom): 设置显示区域,即设置裁剪区。 translate(float x, float y): 平移画布。 rotate(float degree, float px, float py): 旋转画布 。 skew(float sx, float sy): 设置偏移量。 save(): 将Canvas当前状态保存在堆栈,save之后可以调用Canvas的平移、旋转、错切、剪裁等操作。 restore(): 恢复为之前堆栈保存的Canvas状态,防止save后对Canvas执行的操作对后续的绘制有影响。restore和save要配对使用,restore可以比save少,但不能比save多,否则会引发error。save和restore之间,往往夹杂的是对Canvas的特殊操作。 save(int num):将Canvas当前状态保存在堆栈,并予以编号int restoreToCount(int num):恢复为之前堆栈保存的编号为int的Canvas状态 concat(Matrix matrix):画布关联矩阵,画出来的内容按矩阵改变,而不是画布改变。 Drawable.draw(Canvas canvas):将Drawable画到Canvas中 注:这种方式画Drawable怎么设置透明度呢?((BitmapDrawable)Drawable).getPaint().setAlpha(mBgAlpha);
2.2 绘制常见图形【重点】
- 绘制矩形
drawRect(RectF rect, Paint paint) 绘制矩形,参数一为RectF一个区域 drawRect(float left, float top, float right, float bottom, Paint paint) 绘制矩形,left:矩形left的x坐标,top:矩形top的y坐标,right:矩形right的x坐标,bottom:矩形bottom的y坐标 drawRoundRect(RectF rect, float rx, float ry, Paint paint) 绘制圆角矩形, rx:x方向的圆角半径,ry:y方向的圆角半径 drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 绘制圆角矩形,left:矩形left的x坐标,top:矩形top的y坐标,right:矩形right的x坐标,bottom:矩形bottom的y坐标,rx:x方向的圆角半径,ry:y方向的圆角半径
- 绘制圆形或者椭圆
drawOval(RectF oval, Paint paint) 绘制椭圆,参数一是扫描区域,参数二为paint对象 drawOval(float left, float top, float right, float bottom, Paint paint) drawCircle(float cx, float cy, float radius,Paint paint) 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象; drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 画弧,参数一是RectF对象,指定圆弧的外轮廓矩形区域,参数二是起始角(度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;
- 绘制文本
drawText(String text, float x, floaty, Paint paint) 渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二文字左侧到x轴距离,参数三文字BaseLine到y轴距离,参数四是Paint对象。
- 绘制图片
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) 贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。 drawBitmap (Bitmap bitmap, float left, float top, Paint paint) 绘制bitmap图片,
- 绘制其他
canvas.drawPaint(Paint paint) 将画笔设置的颜色和透明度铺满画布 drawPath(Path path, Paint paint) 绘制一个路径,参数一为Path路径对象 drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) 画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象。 drawPoint(float x, float y, Paint paint) 画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。
2.3 Canvas对象的获取方式
- Canvas对象的获取方式有两种:
- 第一种通过重写View.onDraw方法,View中的Canvas对象会被当做参数传递过来,操作这个Canvas,效果会直接反应在View中。
- 第二种通过new创建一个Canvas对象
- 代码如下所示
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } Canvas canvas = new Canvas();
2.4 Canvas的作用
- Canvas可以绘制的对象有:弧线(arcs)、填充颜色(argb和color)、Bitmap、圆(circle和oval)、点(point)、线(line)、矩形(Rect)、图片(Picture)、圆角矩形(RoundRect)、文本(text)、顶点(Vertices)、路径(path)。
2.5 Canvas绘制圆和椭圆
- 绘制圆
private Paint paint = new Paint(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); paint.setColor(Color.BLUE); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(200,200,100 , paint); }
2.6 Canvas绘制矩形、圆角矩形
- 如下所示
image private Paint paint = new Paint(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); canvas.drawRect(100, 100, 200, 200, paint); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { canvas.drawRoundRect(400, 100, 600, 300, 30, 30, paint); } paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(20); canvas.drawRect(100, 400, 300, 600, paint); }
2.7 Canvas绘制文字
- Canvas绘制文字
image private Paint paint = new Paint(); @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setTextSize(100); canvas.drawText("潇湘剑雨", 100, 100, paint); }
2.8 Canvas绘制弧形、封闭弧形
- 绘制弧形、封闭弧形
image private Paint paint = new Paint(); @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); paint.setColor(Color.RED); RectF rel = new RectF(50, 50, 150, 150); //实心圆弧 canvas.drawArc(rel, 0, 135, false, paint); //实心圆弧 将圆心包含在内 RectF rel2 = new RectF(50, 200, 150, 300); canvas.drawArc(rel2, 0, 135, true, paint); //设置空心Style paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(20); RectF rel3 = new RectF(50, 350, 150, 450); canvas.drawArc(rel3, 0, 270, false, paint); RectF rel4 = new RectF(50, 250, 150, 600); canvas.drawArc(rel4, 0, 270, true, paint); }
2.9 Canvas绘制Path路径
- Canvas绘制Path路径
image private Paint paint = new Paint(); @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Path angle = new Path(); angle.moveTo(250, 0); angle.lineTo(0, 500); angle.lineTo(100, 300); angle.lineTo(200, 350); angle.lineTo(500, 500); angle.close(); canvas.drawPath(angle, paint); }
03.Matrix变换矩阵【Canvas位置转换】
- 思考:如果要画一个仪表盘(数字围绕显示在一个圆圈中),或者类似钟表指针样的控件,如何实现?
- Android还提供了一些对Canvas位置转换的方法:rorate、scale、translate、skew(扭曲)等,而且它允许你通过获得它的转换矩阵对象(getMatrix方法)直接操作它。
- 这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。为了方便一些转换操作,Canvas还提供了保存和回滚属性的方法(save和restore),比如你可以先保存目前画纸的位置(save),然后旋转90度,向下移动100像素后画一些图形,画完后调用restore方法返回到刚才保存的位置。
3.1 translate平移
3.2 rorate旋转
- rorate旋转
image private Paint mPaint = new Paint(); @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); mPaint.setColor(Color.RED); canvas.drawRect(new Rect(0, 0, 800, 800), mPaint); canvas.save(); mPaint.setColor(Color.GREEN); canvas.rotate(45,400,400); canvas.drawRect(new Rect(0, 0, 800, 800), mPaint); canvas.restore(); }
- 源代码有两个可以使用的方法:
/** * Preconcat the current matrix with the specified rotation. * @param degrees The amount to rotate, in degrees */ public native void rotate(float degrees); /** * Preconcat the current matrix with the specified rotation. * @param degrees The amount to rotate, in degrees * @param px The x-coord for the pivot point (unchanged by the rotation) * @param py The y-coord for the pivot point (unchanged by the rotation) */ public final void rotate(float degrees, float px, float py) { translate(px, py); rotate(degrees); translate(-px, -py); }
3.3 scale缩放
- scale缩放
image private Paint mPaint = new Paint(); @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.YELLOW); mPaint.setColor(Color.RED); canvas.drawRect(new Rect(0, 0, 800, 800), mPaint); // 保存画布状态 canvas.save(); canvas.scale(0.5f, 0.5f); mPaint.setColor(Color.GREEN); canvas.drawRect(new Rect(0, 0, 800, 800), mPaint); // 画布状态回滚 canvas.restore(); canvas.scale(0.5f, 0.5f, 400, 400); mPaint.setColor(Color.BLUE); canvas.drawRect(new Rect(0, 0, 800, 800), mPaint); }
- 源码如下所示
/** * Preconcat the current matrix with the specified scale. * @param sx The amount to scale in X * @param sy The amount to scale in Y */ public native void scale(float sx, float sy); /** * Preconcat the current matrix with the specified scale. * @param sx The amount to scale in X * @param sy The amount to scale in Y * @param px The x-coord for the pivot point (unchanged by the scale) * @param py The y-coord for the pivot point (unchanged by the scale) */ public final void scale(float sx, float sy, float px, float py) { translate(px, py); scale(sx, sy); translate(-px, -py); }
3.4 skew扭曲
4.Rect和RectF介绍
4.0 Rect和RectF区别
- Rect的参数为int类型,而RectF的参数类型为float类型,从这一点上来看,RectF的精度更高一些,但是他们都是通过四个坐标参数来确定一个矩形的区域。
- 精度不一样,Rect是使用int类型作为数值,RectF是使用float类型作为数值。两个类型提供的方法也不是完全一致,但大多数都差不多。
4.1 Rect简单属性
- 这是一个我们常用的一个“绘画相关的工具类”,常用语描述长方形/正方形,他只有4个属性
public int left; public int top; public int right; public int bottom;
- 其中常用的构造方法如下所示
public Rect(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public Rect(Rect r) { if (r == null) { left = top = right = bottom = 0; } else { left = r.left; top = r.top; right = r.right; bottom = r.bottom; } }
- 这4个属性描述着这一个“方块”,但是这有一个知识点需要理清楚,先看这张图
image
4.2 Rect父类的实现
- 实现了Parcelable 所以需要实现一堆Object的方法,诸如equals,toString等等,来简单看一看
- 对于equals方法,首先先对传来的对象进行判空,类型判断,再强转成Rect对象,最后还是一个个去比对那4个属性。
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Rect r = (Rect) o; return left == r.left && top == r.top && right == r.right && bottom == r.bottom; } @Override public int hashCode() { int result = left; result = 31 * result + top; result = 31 * result + right; result = 31 * result + bottom; return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(32); sb.append("Rect("); sb.append(left); sb.append(", "); sb.append(top); sb.append(" - "); sb.append(right); sb.append(", "); sb.append(bottom); sb.append(")"); return sb.toString(); }
4.3 Rect常用的一些方法
- 获取“宽”
//文章开头说的公式在这里得到了应验 public final int width() { return right - left; }
- 获取“高”
public final int height() { return bottom - top; }
- 有效性的判断
//因为left是最左侧,right比left还小不就不成形了么?宽高同是如此 public final boolean isEmpty() { return left >= right || top >= bottom; }
- 全部置0操作
public void setEmpty() { left = right = top = bottom = 0; }
- 设置参数方法,和构造函数的区别仅在于不会创建新对象
public void set(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; }
05.关于几个案例
- 上面比较详细介绍了Canvas,Paint,Matrix,RectF等等的属性,作用,常用方法,接下来就需要结合具体业务需求练手写一下小案例自定义控件呢
5.1 自定义轮播图圆点
- 5.1.1 需求介绍
- 绘制圆环,一个实心中心圆,还有一个外圆环
- 此控件可以设置宽度和高度,可以设置颜色
- 5.1.2 思路介绍
- 3.2.1 既然是绘制圆形,可以写一个继承View的自定义view
- 3.2.2 重写onDraw方法,获取控件宽高,然后比较宽高值,取小值的一半作为圆的半径
- 3.2.3 然后分别绘制选中状态和未选中状态的圆
- 3.2.4 创建画笔Paint,并且设置相关属性,比如画笔颜色,类型等
- 3.2.5 利用canvas绘制圆,然后再又用相同方法绘制外边缘
- 3.2.6 自定义一个是否选中状态的方法,传入布尔值是否选中,然后调用view中invalidate方法
- 5.1.3 代码介绍
- 具体代码如下所示:
/** * <pre> * @author yangchong * blog : https://github.com/yangchong211 * time : 2016/5/18 * desc : 红点自定义控件 * revise: 建议设置红点宽高一样,否则是椭圆 * </pre> */ public class DotView extends View { private boolean isInit = false; private boolean isSelected = false; private float mViewHeight; private float mViewWidth; private float mRadius; private Paint mPaintBg = new Paint(); private int mBgUnselectedColor = Color.parseColor("#1A000000"); private int mBgSelectedColor = Color.parseColor("#FDE26E"); private static final float mArcWidth = 2.0f; public DotView(Context context) { super(context); } public DotView(Context context, AttributeSet attrs) { super(context, attrs); } public DotView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isInit) { isInit = true; mViewHeight = getHeight(); mViewWidth = getWidth(); if (mViewHeight >= mViewWidth) { mRadius = mViewWidth / 2.f; } else { mRadius = mViewHeight / 2.f; } } //是否选中 if (isSelected){ drawSelectedDot(canvas); } else{ drawUnSelectedDot(canvas); } } /** * 绘制选中指示器红点 * @param canvas canvas */ private void drawSelectedDot(Canvas canvas) { //设置paint相关属性 mPaintBg.setAntiAlias(true); mPaintBg.setColor(mBgSelectedColor); mPaintBg.setStyle(Style.FILL); //绘制圆 canvas.drawCircle(mViewWidth / 2.f, mViewHeight / 2.f, mRadius - 8.f, mPaintBg); mPaintBg.setStyle(Style.STROKE); float offset = 1.f + mArcWidth; RectF oval = new RectF(mViewWidth / 2.f - mRadius + offset, mViewHeight / 2.f - mRadius + offset, mViewWidth / 2.f + mRadius - offset, mViewHeight / 2.f + mRadius - offset); //绘制指定的弧线,该弧线将被缩放以适应指定的椭圆形。 canvas.drawArc(oval, 0.f, 360.f, false, mPaintBg); } /** * 绘制未选中指示器红点 * @param canvas canvas */ private void drawUnSelectedDot(Canvas canvas) { mPaintBg.setAntiAlias(true); mPaintBg.setColor(mBgUnselectedColor); mPaintBg.setStyle(Style.FILL); canvas.drawCircle(mViewWidth / 2.f, mViewHeight / 2.f, mRadius - 8.f, mPaintBg); } /** * 设置是否选中 * @param isSelected isSelected */ public void setIsSelected(boolean isSelected) { this.isSelected = isSelected; //使整个视图无效。如果视图是可见的,则{@link#onDraw(android.Graphics.Canvas)}将在将来的某个时候被调用。 //调用该方法,会进行重新绘制,也就是调用onDraw方法 this.invalidate(); } }
5.2 自定义圆环百分比进度条
- 5.2.1 需求分析
- 1.业务需求:可以设置圆角,可以设置圆形,如果是圆角则必须设置半径,默认圆角半径为10dp
- 2.如果设置了圆形,则即使设置圆角也无效;如果设置非圆形,则圆角生效,同时需要判断圆角半径是否大于控件宽高,处理边界逻辑
- 3.当设置圆形的时候,即使设置宽高不一样,那么取宽高中的最小值的一半为圆形半径
- 5.2.2 代码介绍
- 代码如下所示
public class ARoundImageView extends AppCompatImageView { /* * Paint:画笔 * Canvas:画布 * Matrix:变换矩阵 * * 业务需求:可以设置圆角,可以设置圆形,如果是圆角则必须设置半径,默认圆角半径为10dp */ /** * 圆形模式 */ private static final int MODE_CIRCLE = 1; /** * 普通模式 */ private static final int MODE_NONE = 0; /** * 圆角模式 */ private static final int MODE_ROUND = 2; /** * 圆角半径 */ private int currRound = dp2px(10); /** * 画笔 */ private Paint mPaint; /** * 默认是普通模式 */ private int currMode = 0; public ARoundImageView(Context context) { this(context,null); } public ARoundImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ARoundImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); obtainStyledAttrs(context, attrs, defStyleAttr); initViews(); } private void obtainStyledAttrs(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ARoundImageView, defStyleAttr, 0); currMode = a.hasValue(R.styleable.ARoundImageView_type) ? a.getInt(R.styleable.ARoundImageView_type, MODE_NONE) : MODE_NONE; currRound = a.hasValue(R.styleable.ARoundImageView_radius) ? a.getDimensionPixelSize(R.styleable.ARoundImageView_radius, currRound) : currRound; a.recycle(); } private void initViews() { //ANTI_ALIAS_FLAG 用于绘制时抗锯齿 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); } /** * 当模式为圆形模式的时候,我们强制让宽高一致 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (currMode == MODE_CIRCLE) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int result = Math.min(getMeasuredHeight(), getMeasuredWidth()); // 此方法必须由{@link#onMeasure(int,int)}调用,以存储已测量的宽度和测量的高度。 // 如果不这样做,将在测量时触发异常。 setMeasuredDimension(result, result); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { //获取ImageView图片资源 Drawable mDrawable = getDrawable(); //获取Matrix对象 Matrix mDrawMatrix = getImageMatrix(); if (mDrawable == null) { return; } if (mDrawable.getIntrinsicWidth() == 0 || mDrawable.getIntrinsicHeight() == 0) { return; } if (mDrawMatrix == null && getPaddingTop() == 0 && getPaddingLeft() == 0) { mDrawable.draw(canvas); } else { final int saveCount = canvas.getSaveCount(); canvas.save(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (getCropToPadding()) { final int scrollX = getScrollX(); final int scrollY = getScrollY(); canvas.clipRect(scrollX + getPaddingLeft(), scrollY + getPaddingTop(), scrollX + getRight() - getLeft() - getPaddingRight(), scrollY + getBottom() - getTop() - getPaddingBottom()); } } canvas.translate(getPaddingLeft(), getPaddingTop()); switch (currMode){ case MODE_CIRCLE: Bitmap bitmap1 = drawable2Bitmap(mDrawable); mPaint.setShader(new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint); break; case MODE_ROUND: Bitmap bitmap2 = drawable2Bitmap(mDrawable); mPaint.setShader(new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); canvas.drawRoundRect(new RectF(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()), currRound, currRound, mPaint); break; case MODE_NONE: default: if (mDrawMatrix != null) { canvas.concat(mDrawMatrix); } mDrawable.draw(canvas); break; } canvas.restoreToCount(saveCount); } } /** * drawable转换成bitmap */ private Bitmap drawable2Bitmap(Drawable drawable) { if (drawable == null) { return null; } Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); //根据传递的scaleType获取matrix对象,设置给bitmap Matrix matrix = getImageMatrix(); if (matrix != null) { canvas.concat(matrix); } drawable.draw(canvas); return bitmap; } private int dp2px(float value) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); } }