BitmapShader
: 使用着色器Xfermode
:使用图层叠加ClipPath
:通过对画布裁剪的方式RoundedBitmapDrawable
: 系统API圆角类前面三种是通过继承
ImageView
重写onDraw()
方法实现
最后一种是系统API直接使用。
每一种方式都能实现显示圆形图片, 我们主要从以下几个方面来比较各个方式的不同
- 实现方式难易
- 空白的背景
- 抗锯齿的能力
第1种方式:BitmapShader
实现
通过对Paint
(画笔)设置着色器(Shader
)来实现,这里的Shader是指BitmapShader
,实际上有很多的Shader
,根据需要进行使用。
也就是说,当BitmapShader
设置在画笔上的时候,画笔画出来的内容就是一张bitmap
图,既然画的内容是Bitmap
,那么画的形状如果是圆形,这就是一个显示圆形的ImageView
了,当然也可以画圆角,画正方形。
//进行画笔初始化
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);//给画笔设置抗锯齿
mMatrix = new Matrix();
//该方法千万别放到onDraw()方法里面调用,否则会不停的重绘的,因为该方法调用了invalidate() 方法
setLayerType(View.LAYER_TYPE_SOFTWARE, null);//禁用硬加速
}
//重写onDraw()方法获取BitmapDrawable进行处理
@Override
protected void onDraw(Canvas canvas) {
Drawable drawable = getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
//通过BitmapShader的方式实现
drawRoundByShaderMode(canvas, bitmap);
} else {
super.onDraw(canvas);
}
}
private void drawRoundByShaderMode(Canvas canvas, Bitmap bitmap) {
//获取到Bitmap的宽高
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//获取到ImageView的宽高
int viewWidth = getWidth();
int viewHeight = getHeight();
//根据Bitmap生成一个BitmapShader,这里有个TileMode设置有三个参数可以选择:
//CLAMP, MIRROR, REPEAT 在后面会细说。
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mMatrix.reset();
float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
mMatrix.setScale(minScale, minScale);//将Bitmap保持比列根据ImageView的大小进行缩放
bitmapShader.setLocalMatrix(mMatrix); //将矩阵变化设置到BitmapShader,其实就是作用到Bitmap
mPaint.setShader(bitmapShader);
//绘制圆形
canvas.drawCircle(bitmapWidth / 2, bitmapHeight / 2, Math.min(bitmapWidth / 2, bitmapHeight / 2), mPaint);
}
//RoundImageView在XML中的布局
<com.jabo.douban.demo.test.RoundImageViewTest
android:layout_width="90dp"
android:layout_height="90dp"
android:src="@drawable/test_view"
app:layout_constraintBottom_toTopOf="@+id/guideline6"
app:layout_constraintEnd_toStartOf="@+id/guideline5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
//以上就是全部关键代码,即可实现一个简单的圆形ImageView
上面第33行中Bitmap
的TileMode
,其实就是说如果Bitmap
的内容没有铺满View
时,如何填充形状里面剩余的部分,我们来看一下
ClAMP
: Bitmap
以其内容的最后一行像素填充剩余的高的空白或者最后一列像素填充剩余宽空白MIRROR
:Bitmap
以其内容以镜像的方式填充剩余空白REPEAT
:Bitmap
以其内容以重复的方式填充剩余空白现在来看一下抗锯齿的能力,上面的图都是设置了抗锯齿的,如果把尺寸设置小一点:
表现还不错,为了进行明显的对比,我们去掉抗锯齿,对比下
// 注释掉抗锯齿 // mPaint.setAntiAlias(true);//给画笔设置抗锯齿
// 加上抗锯齿 mPaint.setAntiAlias(true);//给画笔设置抗锯齿
所以关于BitmapShader
的实现方式的比较
第2种方式:Xfermode
实现
原理其实就是通过叠加显示的方式,设置不同的Xformode导致叠加的策略不同,最终显示不同,来一张图
PorterDuff.Mode
CLEAR
清除图像(源覆盖的目标像素被清除为0)
SRC
只显示源图像(源像素替换目标像素)
DST
只显示目标图像(源像素被丢弃,使目标保持完整)
SRC_OVER
将源图像放在目标图像上方
DST_OVER
将目标图像放在源图像上方(源像素是在目标像素后面绘制的。)
SRC_IN
只在源图像和目标图像相交的地方绘制【源图像】(保持源像素覆盖目标像素,丢弃剩余的源和目标像素)
DST_IN
只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
SRC_OUT
只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
DST_OUT
只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤(保持目标像素不被源像素所覆盖。丢弃由源像素覆盖的目标像素。丢弃所有源像素。)
SRC_ATOP
在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
DST_ATOP
在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
XOR
在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制
DARKEN
变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
LIGHTEN
变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
MULTIPLY
正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
SCREEN
滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖(添加源和目标像素,然后减去源像素乘以目标。)
ADD
饱和相加,对图像饱和度进行相加,不常用
OVERLAY
叠加
现在来看代码
private void drawRoundByXfermode(Canvas canvas, Bitmap bitmap) {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
int viewWidth = getWidth();
int viewHeight = getHeight();
float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
mMatrix.reset();
mMatrix.setScale(minScale, minScale);
//经过矩阵变换后的新的bitmap
Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, mMatrix, true);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawCircle(viewWidth / 2, viewWidth / 2, Math.min(viewHeight / 2, viewHeight / 2), mPaint);
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(newBitmap, (viewWidth - newBitmap.getWidth()) / 2, (viewHeight - newBitmap.getHeight()) / 2, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
效果图
很明显XferMode
的方式能够留出空白,并且通过这种叠加的方式,能够实现任何形状的显示,扩展性可以说是极好的。
现在来看一下锯齿,
这是注释掉
XforMode
锯齿之后的效果
下面是
Xformode
加上去锯齿的效果
可以看到效果非常的平滑
所以关于Xfermode
的实现方式的比较
第3种方式:Canvas.clipPath()
实现
通过裁剪画布的方式实现,先将画布裁剪成圆形,在绘制bitmap
private void drawRoundByClipPath(Canvas canvas, Bitmap bitmap) {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
int viewWidth = getWidth();
int viewHeight = getHeight();
float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
mMatrix.reset();
mMatrix.setScale(minScale, minScale);
//经过矩阵变换后的新的bitmap
Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, mMatrix, true);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//Path.Direction.CCW 这个参数表示path的方向,书顺时针还是逆时针,这里是逆时针
mPath.addCircle(viewWidth / 2, viewWidth / 2, Math.min(viewHeight / 2, viewHeight / 2), Path.Direction.CCW);
mPaint.reset();
mPaint.setAntiAlias(true);//对画笔设置抗锯齿
canvas.setDrawFilter(pdf);//对画布设置抗锯齿
canvas.clipPath(mPath);
//将Bitmap绘制到中心区域
canvas.drawBitmap(newBitmap, (viewWidth - newBitmap.getWidth()) / 2, (viewHeight - newBitmap.getHeight()) / 2, mPaint);
canvas.restoreToCount(layerId);
}
效果展示
通过这种方式也能实现圆形裁剪,我们可以在背后设置draw
一个circle
实现白色背景,但是这不重要。
重要的是我们代码中设置了抗锯齿,但是还是有比较明显锯齿。
也就是说,代码中设置了抗锯齿和不设置都有明显的锯齿。
所以关于Xfermode
的实现方式的比较
第4种方式:RoundedBitmapDrawable
实现(系统API)
系统为我们提供了一个类来实现圆角功能,这就不需要我们通过继承ImageView并且重新ondraw()来实现了。
private void initRoundedDrawable() {
ImageView view = findViewById(R.id.ivRoundedView);
Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.test_view); //获取Bitmap图片
RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), src); //创建RoundedBitmapDrawable对象
roundedBitmapDrawable.setCornerRadius(500); //设置圆角Radius(根据实际需求)
roundedBitmapDrawable.setAntiAlias(true); //设置抗锯齿
view.setImageDrawable(roundedBitmapDrawable); //显示圆角
}
这种方式的话,实际上使用的是BitmapShader
的方式实现的,我们可以看一下他设置圆角的核心代码
public void setCornerRadius(float cornerRadius) {
if (mCornerRadius == cornerRadius) return;
mIsCircular = false;
if (isGreaterThanZero(cornerRadius)) {
mPaint.setShader(mBitmapShader); //这里设置了BitmapShader
} else {
mPaint.setShader(null);
}
mCornerRadius = cornerRadius;
invalidateSelf();
}
所以锯齿能力我们就不再分析了,参照BitmapShader
方式。
所以关于RoundedBitmapDrawable
的实现方式的比较
最后总结一下,其实每种方式都能实现圆形,但是需要我们根据自身的需求去选择。
BitmapShader |
Xfermode |
clipPath |
RoundedBitmapDrawable |
|
---|---|---|---|---|
实现方式难易 | 简单 | 简单 | 简单 | 最简单 |
空白的背景 | 不能设置 | 可以设置 | 可以设置 | 不能设置 |
抗锯齿 | 可以设置,有效 | 可以设置,有效 | 不可去除 | 可以设置,有效 |
推荐指数 | 推荐 | 推荐 | 不推荐 | 推荐 |
如果觉得有用,
点个赞吧
!!!