reoger的记录

--以后的你会感激现在那么努力的自己

0%

自定义View之将图片裁剪成圆形图片

自定义View之将图片裁剪成圆形图片

使用PorterDuffXfermode方式来实现

在使用这中方式之前,我们有必要弄清楚PorterBuddx究竟是什么。
简单来说,他是定义的一种图像融合的方式,具体的模式有如下几种,效果如图:
官方的说明图

在次,不多做介绍,想深入了解PorterDuffXfermode可以参考各个击破搞明白PorterDuff.Mode
本次使用mode是SRCIN,由图效果可知,我们需要在原图上绘制一个圆,原图与我们绘制得圆重叠得区域就会显示出来,原理很简单,下面开始代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class CircleImageView1 extends ImageView {

private Paint mPaint;
private Shape mShape;
private float mRadius;

private float[] outerRadii = new float[8];;

public CircleImageView1(Context context) {
this(context,null);
}

public CircleImageView1(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}

public CircleImageView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}

private void initView() {
setLayerType(LAYER_TYPE_HARDWARE,null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLACK);
//注意这里,将画笔的PorterDuffXfermode模式设置为了DST_IN模式!!!
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int min = Math.min(width,height);
mRadius = min/2;
setMeasuredDimension(min,min);
//因为是圆,所以要设置为长宽相等
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed){
if (mShape == null){
Arrays.fill(outerRadii,mRadius);
mShape = new RoundRectShape(outerRadii,null,null);
//
}
mShape.resize(getWidth(),getHeight());
}

}

@Override
protected void onDraw(Canvas canvas) {
int saveCount = canvas.getSaveCount();
canvas.save();
super.onDraw(canvas);
if (mShape != null){
mShape.draw(canvas,mPaint);
}
canvas.restoreToCount(saveCount);
}

// 这里直接使用Glide库来加载图片,也可以在xml文件或者java代码中设置图片
public void build(String url){
Glide.with(getContext()).load(url).into(this);
}
}

在布局中的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#333"
tools:context="com.example.cm.circleview.MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">

<com.example.cm.circleview.view.CircleImageView1
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:id="@+id/image_id1"
android:layout_width="200dp"
android:layout_height="200dp" />
</LinearLayout>

</ScrollView>

通过java代码设置图片来源,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity {

public static final String URL = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533271076118&di=" +
"4ef2b0b50abb957177874a76d828126a&imgtype=0&src=http%3A%2F%2Fwww.91danji.com%2Fattachments%2F201503%2F24%2F11%2F3q8s74tc8.jpeg";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

CircleImageView1 circleImageView1 = findViewById(R.id.image_id1);
circleImageView1.build(URL);
}
}

因为项目使用到了Glide,所以需要在module的build.grade文件中添加必要的依赖,

1
implementation 'com.github.bumptech.glide:glide:3.8.0'

因为图片来自网路,需在androidMainFest中添加联网权限。
显示效果如图:
效果图

此次的实现都是最简单的实现,没有添加额外的逻辑,代码比较少,便于理解。
后续会在这个的基础上添加稍微复杂点的图像处理。

使用小结

通过该方式使得图片展现成圆形,需要开启硬件加速,否则图片的渲染效果达不到理想的效果,具体原因可以参照:硬件加速 setlayertype
我们可以通过shape或者drawable来绘制圆,使用然后通过图片的叠加显示来显示我们需要的原型图片的效果。这种方式就是通过绘制一个圆盖在原来的图片上,通过设置两张图片叠加显示效果来实现显示圆角图片

优点

该方式不需要操作原来的drawable,使用起来风险比较小,也比较方便。

缺点

需要启动硬件加速,否则可能会出现意想不到的结果。

使用BitmapShader重新绘制实现圆形图片

整体思路: 通过对原来图片进行处理,将原图片裁剪成圆形然后绘制展示。
贴上CircleImageView2的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class CircleImageView2 extends ImageView {

private Paint mPaint;
private int mRadius;
private float mScale;
private Matrix mMatrix;

private BitmapShader mBitmapShader;

public static final String TAG = "CircleImageView2";

public CircleImageView2(Context context) {
this(context, null);
}

public CircleImageView2(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public CircleImageView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(attrs);
}

private void initView(AttributeSet attrs) {

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mMatrix = new Matrix();
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int min = Math.min(getMeasuredHeight(), getMeasuredWidth());
mRadius = min / 2;
setMeasuredDimension(min, min);
//强制设置成宽度和高度一致
}

@Override
protected void onDraw(Canvas canvas) {
if (mBitmapShader == null) {
Bitmap bitmap = drawableToBitmap(getDrawable());
//CLAMP表示,当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。
//REPEAT表示,当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域
//MIRROR与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//计算缩放比例
mScale = (mRadius * 2.0f) / Math.min(bitmap.getHeight(), bitmap.getWidth());
mMatrix.setScale(mScale, mScale);
}
//设置图片缩放
mBitmapShader.setLocalMatrix(mMatrix);
mPaint.setShader(mBitmapShader);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
}

// 将圆图片转化成bitmap
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
//ARGB_8888 表示颜色色由4个8位组成即32位
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
return bitmap;
}

public void build(String url) {
Glide.with(getContext())
.load(url)
.into(this);
}
}

实现逻辑:
首先将获得的drawable转化成bitmap,然后通过BitmapShader处理bitmap,并将其设置给画笔,当使用此画笔绘制时,该bitmap就会展示出来,如此我们只需要使用画布绘制一个圆形即可。

优点

可以实现对图片复杂的处理,可扩展性较强。

缺点

实现起来较为复杂,且有对

通过clipPath裁剪的方式

这种方式最简单暴力,直接在原来的图片上进行裁剪就ok了,如果不需要对图片进行处理的画,推荐用这种方式,因为,简单,粗暴!
直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class CircleImageView3 extends ImageView {

private float mRadius;
private Path mPath;

public CircleImageView3(Context context) {
this(context,null);
}

public CircleImageView3(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public CircleImageView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}

private void initView() {
mPath = new Path();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int min = Math.min(getMeasuredHeight(),getMaxWidth());
mRadius = min/2;
}

@Override
protected void onDraw(Canvas canvas) {
mPath.addCircle(mRadius, mRadius, mRadius,Path.Direction.CW);
canvas.clipPath(mPath);
super.onDraw(canvas);
}

public void build(String url) {
Glide.with(getContext())
.load(url)
.into(this);
}
}

没什么好说的,关键就一句canvas.clipPath(mPath);,需要关注一下path的用法。

优点

简单粗暴,不涉及drwable 相关的操作,效率较高,实现也简单。

缺点

因为是对图片的直接剪裁,不能实现对图片相关的处理,也没有抗锯齿效果,如果图片不是正方形图片可能会影响最终的效果。

源程序下载地址:https://github.com/Reoger/CurtomViewTest

总结

上述三种方式都能实现圆形图片的需求,但是各有优缺点。如果只是需要展示圆形图片而不需要对图片进行任何处理,可以选用第三种方式,需要对图片进行比较复杂的处理的画,则推荐第一种或者第二种方式。
本篇博客到此结束,下一篇将会使用上述的三种方式实现圆形外边框的效果。

参考链接